From 42970665fcab56316b32fadc1b09eec8ecadc207 Mon Sep 17 00:00:00 2001 From: Jelte Fennema Date: Wed, 8 Feb 2023 11:22:55 +0100 Subject: [PATCH 1/7] Add linting and formatting tools for python --- .flake8 | 7 + pyproject.toml | 5 + src/test/regress/Pipfile | 4 + src/test/regress/Pipfile.lock | 274 +++++++++++++++++++++++----------- 4 files changed, 206 insertions(+), 84 deletions(-) create mode 100644 .flake8 create mode 100644 pyproject.toml diff --git a/.flake8 b/.flake8 new file mode 100644 index 000000000..a52bd806a --- /dev/null +++ b/.flake8 @@ -0,0 +1,7 @@ +[flake8] +# Whitespace before ':' (E203) is ignored for black +# Module level import not at top of file (E402) is ignored because of the way we do relative imports +extend-ignore = E203, E402 +# black will truncate to 88 characters usually, but long string literals it +# might keep. That's fine in most cases unless it gets really excessive. +max-line-length = 150 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..a470b2d92 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,5 @@ +[tool.isort] +profile = 'black' + +[tool.black] +include = '(src/test/regress/bin/diff-filter|\.pyi?|\.ipynb)$' diff --git a/src/test/regress/Pipfile b/src/test/regress/Pipfile index 1663704f3..240dee3df 100644 --- a/src/test/regress/Pipfile +++ b/src/test/regress/Pipfile @@ -10,6 +10,10 @@ docopt = "==0.6.2" cryptography = "==3.4.8" [dev-packages] +black = "*" +isort = "*" +flake8 = "*" +flake8-bugbear = "*" [requires] python_version = "3.9" diff --git a/src/test/regress/Pipfile.lock b/src/test/regress/Pipfile.lock index a336cd1c5..954c3610e 100644 --- a/src/test/regress/Pipfile.lock +++ b/src/test/regress/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "09fefbec76b9344107dfed06002546b50adca38da844b604fe8581a7a14fd656" + "sha256": "635b4c111e3bca87373fcdf308febf0a816dde15b14f6bf078f2b456630e5ef1" }, "pipfile-spec": 6, "requires": { @@ -122,7 +122,7 @@ "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3", "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18" ], - "index": "pypi", + "markers": "python_version >= '3.6'", "version": "==2022.12.7" }, "cffi": { @@ -315,49 +315,59 @@ }, "markupsafe": { "hashes": [ - "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003", - "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88", - "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5", - "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7", - "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a", - "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603", - "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1", - "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135", - "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247", - "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6", - "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601", - "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77", - "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02", - "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e", - "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63", - "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f", - "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980", - "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b", - "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812", - "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff", - "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96", - "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1", - "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925", - "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a", - "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6", - "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e", - "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f", - "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4", - "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f", - "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3", - "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c", - "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a", - "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417", - "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a", - "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a", - "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37", - "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452", - "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933", - "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a", - "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7" + "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed", + "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc", + "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2", + "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460", + "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7", + "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0", + "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1", + "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa", + "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03", + "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323", + "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65", + "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013", + "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036", + "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f", + "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4", + "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419", + "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2", + "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619", + "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a", + "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a", + "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd", + "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7", + "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666", + "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65", + "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859", + "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625", + "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff", + "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156", + "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd", + "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba", + "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f", + "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1", + "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094", + "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a", + "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513", + "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed", + "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d", + "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3", + "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147", + "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c", + "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603", + "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601", + "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a", + "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1", + "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d", + "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3", + "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54", + "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2", + "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6", + "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58" ], "markers": "python_version >= '3.7'", - "version": "==2.1.1" + "version": "==2.1.2" }, "mitmproxy": { "editable": true, @@ -518,46 +528,6 @@ "markers": "python_version >= '3'", "version": "==0.17.16" }, - "ruamel.yaml.clib": { - "hashes": [ - "sha256:045e0626baf1c52e5527bd5db361bc83180faaba2ff586e763d3d5982a876a9e", - "sha256:15910ef4f3e537eea7fe45f8a5d19997479940d9196f357152a09031c5be59f3", - "sha256:184faeaec61dbaa3cace407cffc5819f7b977e75360e8d5ca19461cd851a5fc5", - "sha256:1f08fd5a2bea9c4180db71678e850b995d2a5f4537be0e94557668cf0f5f9497", - "sha256:2aa261c29a5545adfef9296b7e33941f46aa5bbd21164228e833412af4c9c75f", - "sha256:3110a99e0f94a4a3470ff67fc20d3f96c25b13d24c6980ff841e82bafe827cac", - "sha256:3243f48ecd450eddadc2d11b5feb08aca941b5cd98c9b1db14b2fd128be8c697", - "sha256:370445fd795706fd291ab00c9df38a0caed0f17a6fb46b0f607668ecb16ce763", - "sha256:40d030e2329ce5286d6b231b8726959ebbe0404c92f0a578c0e2482182e38282", - "sha256:41d0f1fa4c6830176eef5b276af04c89320ea616655d01327d5ce65e50575c94", - "sha256:4a4d8d417868d68b979076a9be6a38c676eca060785abaa6709c7b31593c35d1", - "sha256:4b3a93bb9bc662fc1f99c5c3ea8e623d8b23ad22f861eb6fce9377ac07ad6072", - "sha256:5bc0667c1eb8f83a3752b71b9c4ba55ef7c7058ae57022dd9b29065186a113d9", - "sha256:721bc4ba4525f53f6a611ec0967bdcee61b31df5a56801281027a3a6d1c2daf5", - "sha256:763d65baa3b952479c4e972669f679fe490eee058d5aa85da483ebae2009d231", - "sha256:7bdb4c06b063f6fd55e472e201317a3bb6cdeeee5d5a38512ea5c01e1acbdd93", - "sha256:8831a2cedcd0f0927f788c5bdf6567d9dc9cc235646a434986a852af1cb54b4b", - "sha256:91a789b4aa0097b78c93e3dc4b40040ba55bef518f84a40d4442f713b4094acb", - "sha256:92460ce908546ab69770b2e576e4f99fbb4ce6ab4b245345a3869a0a0410488f", - "sha256:99e77daab5d13a48a4054803d052ff40780278240a902b880dd37a51ba01a307", - "sha256:a234a20ae07e8469da311e182e70ef6b199d0fbeb6c6cc2901204dd87fb867e8", - "sha256:a7b301ff08055d73223058b5c46c55638917f04d21577c95e00e0c4d79201a6b", - "sha256:be2a7ad8fd8f7442b24323d24ba0b56c51219513cfa45b9ada3b87b76c374d4b", - "sha256:bf9a6bc4a0221538b1a7de3ed7bca4c93c02346853f44e1cd764be0023cd3640", - "sha256:c3ca1fbba4ae962521e5eb66d72998b51f0f4d0f608d3c0347a48e1af262efa7", - "sha256:d000f258cf42fec2b1bbf2863c61d7b8918d31ffee905da62dede869254d3b8a", - "sha256:d5859983f26d8cd7bb5c287ef452e8aacc86501487634573d260968f753e1d71", - "sha256:d5e51e2901ec2366b79f16c2299a03e74ba4531ddcfacc1416639c557aef0ad8", - "sha256:debc87a9516b237d0466a711b18b6ebeb17ba9f391eb7f91c649c5c4ec5006c7", - "sha256:df5828871e6648db72d1c19b4bd24819b80a755c4541d3409f0f7acd0f335c80", - "sha256:ecdf1a604009bd35c674b9225a8fa609e0282d9b896c03dd441a91e5f53b534e", - "sha256:efa08d63ef03d079dcae1dfe334f6c8847ba8b645d08df286358b1f5293d24ab", - "sha256:f01da5790e95815eb5a8a138508c01c758e5f5bc0ce4286c4f7028b8dd7ac3d0", - "sha256:f34019dced51047d6f70cb9383b2ae2853b7fc4dce65129a5acd49f4f9256646" - ], - "markers": "python_version < '3.10' and platform_python_implementation == 'CPython'", - "version": "==0.2.7" - }, "six": { "hashes": [ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", @@ -667,5 +637,141 @@ "version": "==0.15.2" } }, - "develop": {} + "develop": { + "attrs": { + "hashes": [ + "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836", + "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99" + ], + "markers": "python_version >= '3.6'", + "version": "==22.2.0" + }, + "black": { + "hashes": [ + "sha256:0052dba51dec07ed029ed61b18183942043e00008ec65d5028814afaab9a22fd", + "sha256:0680d4380db3719ebcfb2613f34e86c8e6d15ffeabcf8ec59355c5e7b85bb555", + "sha256:121ca7f10b4a01fd99951234abdbd97728e1240be89fde18480ffac16503d481", + "sha256:162e37d49e93bd6eb6f1afc3e17a3d23a823042530c37c3c42eeeaf026f38468", + "sha256:2a951cc83ab535d248c89f300eccbd625e80ab880fbcfb5ac8afb5f01a258ac9", + "sha256:2bf649fda611c8550ca9d7592b69f0637218c2369b7744694c5e4902873b2f3a", + "sha256:382998821f58e5c8238d3166c492139573325287820963d2f7de4d518bd76958", + "sha256:49f7b39e30f326a34b5c9a4213213a6b221d7ae9d58ec70df1c4a307cf2a1580", + "sha256:57c18c5165c1dbe291d5306e53fb3988122890e57bd9b3dcb75f967f13411a26", + "sha256:7a0f701d314cfa0896b9001df70a530eb2472babb76086344e688829efd97d32", + "sha256:8178318cb74f98bc571eef19068f6ab5613b3e59d4f47771582f04e175570ed8", + "sha256:8b70eb40a78dfac24842458476135f9b99ab952dd3f2dab738c1881a9b38b753", + "sha256:9880d7d419bb7e709b37e28deb5e68a49227713b623c72b2b931028ea65f619b", + "sha256:9afd3f493666a0cd8f8df9a0200c6359ac53940cbde049dcb1a7eb6ee2dd7074", + "sha256:a29650759a6a0944e7cca036674655c2f0f63806ddecc45ed40b7b8aa314b651", + "sha256:a436e7881d33acaf2536c46a454bb964a50eff59b21b51c6ccf5a40601fbef24", + "sha256:a59db0a2094d2259c554676403fa2fac3473ccf1354c1c63eccf7ae65aac8ab6", + "sha256:a8471939da5e824b891b25751955be52ee7f8a30a916d570a5ba8e0f2eb2ecad", + "sha256:b0bd97bea8903f5a2ba7219257a44e3f1f9d00073d6cc1add68f0beec69692ac", + "sha256:b6a92a41ee34b883b359998f0c8e6eb8e99803aa8bf3123bf2b2e6fec505a221", + "sha256:bb460c8561c8c1bec7824ecbc3ce085eb50005883a6203dcfb0122e95797ee06", + "sha256:bfffba28dc52a58f04492181392ee380e95262af14ee01d4bc7bb1b1c6ca8d27", + "sha256:c1c476bc7b7d021321e7d93dc2cbd78ce103b84d5a4cf97ed535fbc0d6660648", + "sha256:c91dfc2c2a4e50df0026f88d2215e166616e0c80e86004d0003ece0488db2739", + "sha256:e6663f91b6feca5d06f2ccd49a10f254f9298cc1f7f49c46e498a0771b507104" + ], + "index": "pypi", + "version": "==23.1.0" + }, + "click": { + "hashes": [ + "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1", + "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb" + ], + "markers": "python_version >= '3.6'", + "version": "==8.0.4" + }, + "flake8": { + "hashes": [ + "sha256:3833794e27ff64ea4e9cf5d410082a8b97ff1a06c16aa3d2027339cd0f1195c7", + "sha256:c61007e76655af75e6785a931f452915b371dc48f56efd765247c8fe68f2b181" + ], + "index": "pypi", + "version": "==6.0.0" + }, + "flake8-bugbear": { + "hashes": [ + "sha256:04a115e5f9c8e87c38bdbbcdf9f58223ffe05469c07c9a7bd8633330bc4d078b", + "sha256:55902ab5a48c5ea53d8689ecd146eda548e72f2724192b9c1d68f6d975d13c06" + ], + "index": "pypi", + "version": "==23.1.20" + }, + "isort": { + "hashes": [ + "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504", + "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6" + ], + "index": "pypi", + "version": "==5.12.0" + }, + "mccabe": { + "hashes": [ + "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", + "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e" + ], + "markers": "python_version >= '3.6'", + "version": "==0.7.0" + }, + "mypy-extensions": { + "hashes": [ + "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", + "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782" + ], + "markers": "python_version >= '3.5'", + "version": "==1.0.0" + }, + "packaging": { + "hashes": [ + "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2", + "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97" + ], + "markers": "python_version >= '3.7'", + "version": "==23.0" + }, + "pathspec": { + "hashes": [ + "sha256:3a66eb970cbac598f9e5ccb5b2cf58930cd8e3ed86d393d541eaf2d8b1705229", + "sha256:64d338d4e0914e91c1792321e6907b5a593f1ab1851de7fc269557a21b30ebbc" + ], + "markers": "python_version >= '3.7'", + "version": "==0.11.0" + }, + "platformdirs": { + "hashes": [ + "sha256:8a1228abb1ef82d788f74139988b137e78692984ec7b08eaa6c65f1723af28f9", + "sha256:b1d5eb14f221506f50d6604a561f4c5786d9e80355219694a1b244bcd96f4567" + ], + "markers": "python_version >= '3.7'", + "version": "==3.0.0" + }, + "pycodestyle": { + "hashes": [ + "sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053", + "sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610" + ], + "markers": "python_version >= '3.6'", + "version": "==2.10.0" + }, + "pyflakes": { + "hashes": [ + "sha256:ec55bf7fe21fff7f1ad2f7da62363d749e2a470500eab1b555334b67aa1ef8cf", + "sha256:ec8b276a6b60bd80defed25add7e439881c19e64850afd9b346283d4165fd0fd" + ], + "markers": "python_version >= '3.6'", + "version": "==3.0.1" + }, + "tomli": { + "hashes": [ + "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", + "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" + ], + "markers": "python_version < '3.11'", + "version": "==2.0.1" + } + } } From 530b24a887296765cd18a64c715e8eb861bd6c51 Mon Sep 17 00:00:00 2001 From: Jelte Fennema Date: Wed, 8 Feb 2023 11:34:49 +0100 Subject: [PATCH 2/7] Format python files with black --- src/test/regress/bin/diff-filter | 107 ++--- .../citus_arbitrary_configs.py | 9 +- src/test/regress/citus_tests/common.py | 28 +- src/test/regress/citus_tests/config.py | 46 +- src/test/regress/citus_tests/run_test.py | 120 ++++-- .../citus_tests/upgrade/pg_upgrade_test.py | 6 +- src/test/regress/mitmscripts/fluent.py | 212 +++++---- src/test/regress/mitmscripts/structs.py | 403 ++++++++++-------- 8 files changed, 542 insertions(+), 389 deletions(-) diff --git a/src/test/regress/bin/diff-filter b/src/test/regress/bin/diff-filter index 960726285..3908ca37f 100755 --- a/src/test/regress/bin/diff-filter +++ b/src/test/regress/bin/diff-filter @@ -10,64 +10,67 @@ import re class FileScanner: - """ - FileScanner is an iterator over the lines of a file. - It can apply a rewrite rule which can be used to skip lines. - """ - def __init__(self, file, rewrite = lambda x:x): - self.file = file - self.line = 1 - self.rewrite = rewrite + """ + FileScanner is an iterator over the lines of a file. + It can apply a rewrite rule which can be used to skip lines. + """ - def __next__(self): - while True: - nextline = self.rewrite(next(self.file)) - if nextline is not None: - self.line += 1 - return nextline + def __init__(self, file, rewrite=lambda x: x): + self.file = file + self.line = 1 + self.rewrite = rewrite + + def __next__(self): + while True: + nextline = self.rewrite(next(self.file)) + if nextline is not None: + self.line += 1 + return nextline def main(): - # we only test //d rules, as we need to ignore those lines - regexregex = re.compile(r"^/(?P.*)/d$") - regexpipeline = [] - for line in open(argv[1]): - line = line.strip() - if not line or line.startswith('#') or not line.endswith('d'): - continue - rule = regexregex.match(line) - if not rule: - raise 'Failed to parse regex rule: %s' % line - regexpipeline.append(re.compile(rule.group('rule'))) + # we only test //d rules, as we need to ignore those lines + regexregex = re.compile(r"^/(?P.*)/d$") + regexpipeline = [] + for line in open(argv[1]): + line = line.strip() + if not line or line.startswith("#") or not line.endswith("d"): + continue + rule = regexregex.match(line) + if not rule: + raise "Failed to parse regex rule: %s" % line + regexpipeline.append(re.compile(rule.group("rule"))) - def sed(line): - if any(regex.search(line) for regex in regexpipeline): - return None - return line + def sed(line): + if any(regex.search(line) for regex in regexpipeline): + return None + return line - for line in stdin: - if line.startswith('+++ '): - tab = line.rindex('\t') - fname = line[4:tab] - file2 = FileScanner(open(fname.replace('.modified', ''), encoding='utf8'), sed) - stdout.write(line) - elif line.startswith('@@ '): - idx_start = line.index('+') + 1 - idx_end = idx_start + 1 - while line[idx_end].isdigit(): - idx_end += 1 - linenum = int(line[idx_start:idx_end]) - while file2.line < linenum: - next(file2) - stdout.write(line) - elif line.startswith(' '): - stdout.write(' ') - stdout.write(next(file2)) - elif line.startswith('+'): - stdout.write('+') - stdout.write(next(file2)) - else: - stdout.write(line) + for line in stdin: + if line.startswith("+++ "): + tab = line.rindex("\t") + fname = line[4:tab] + file2 = FileScanner( + open(fname.replace(".modified", ""), encoding="utf8"), sed + ) + stdout.write(line) + elif line.startswith("@@ "): + idx_start = line.index("+") + 1 + idx_end = idx_start + 1 + while line[idx_end].isdigit(): + idx_end += 1 + linenum = int(line[idx_start:idx_end]) + while file2.line < linenum: + next(file2) + stdout.write(line) + elif line.startswith(" "): + stdout.write(" ") + stdout.write(next(file2)) + elif line.startswith("+"): + stdout.write("+") + stdout.write(next(file2)) + else: + stdout.write(line) main() diff --git a/src/test/regress/citus_tests/arbitrary_configs/citus_arbitrary_configs.py b/src/test/regress/citus_tests/arbitrary_configs/citus_arbitrary_configs.py index 375298148..2d07c0b63 100755 --- a/src/test/regress/citus_tests/arbitrary_configs/citus_arbitrary_configs.py +++ b/src/test/regress/citus_tests/arbitrary_configs/citus_arbitrary_configs.py @@ -115,7 +115,6 @@ def copy_copy_modified_binary(datadir): def copy_test_files(config): - sql_dir_path = os.path.join(config.datadir, "sql") expected_dir_path = os.path.join(config.datadir, "expected") @@ -132,7 +131,9 @@ def copy_test_files(config): line = line[colon_index + 1 :].strip() test_names = line.split(" ") - copy_test_files_with_names(test_names, sql_dir_path, expected_dir_path, config) + copy_test_files_with_names( + test_names, sql_dir_path, expected_dir_path, config + ) def copy_test_files_with_names(test_names, sql_dir_path, expected_dir_path, config): @@ -140,10 +141,10 @@ def copy_test_files_with_names(test_names, sql_dir_path, expected_dir_path, conf # make empty files for the skipped tests if test_name in config.skip_tests: expected_sql_file = os.path.join(sql_dir_path, test_name + ".sql") - open(expected_sql_file, 'x').close() + open(expected_sql_file, "x").close() expected_out_file = os.path.join(expected_dir_path, test_name + ".out") - open(expected_out_file, 'x').close() + open(expected_out_file, "x").close() continue diff --git a/src/test/regress/citus_tests/common.py b/src/test/regress/citus_tests/common.py index cf625c4f7..1b8c69a45 100644 --- a/src/test/regress/citus_tests/common.py +++ b/src/test/regress/citus_tests/common.py @@ -27,13 +27,11 @@ def initialize_temp_dir_if_not_exists(temp_dir): def parallel_run(function, items, *args, **kwargs): with concurrent.futures.ThreadPoolExecutor() as executor: - futures = [ - executor.submit(function, item, *args, **kwargs) - for item in items - ] + futures = [executor.submit(function, item, *args, **kwargs) for item in items] for future in futures: future.result() + def initialize_db_for_cluster(pg_path, rel_data_path, settings, node_names): subprocess.run(["mkdir", rel_data_path], check=True) @@ -52,7 +50,7 @@ def initialize_db_for_cluster(pg_path, rel_data_path, settings, node_names): "--encoding", "UTF8", "--locale", - "POSIX" + "POSIX", ] subprocess.run(command, check=True) add_settings(abs_data_path, settings) @@ -76,7 +74,9 @@ def create_role(pg_path, node_ports, user_name): user_name, user_name ) utils.psql(pg_path, port, command) - command = "SET citus.enable_ddl_propagation TO OFF; GRANT CREATE ON DATABASE postgres to {}".format(user_name) + command = "SET citus.enable_ddl_propagation TO OFF; GRANT CREATE ON DATABASE postgres to {}".format( + user_name + ) utils.psql(pg_path, port, command) parallel_run(create, node_ports) @@ -89,7 +89,9 @@ def coordinator_should_haveshards(pg_path, port): utils.psql(pg_path, port, command) -def start_databases(pg_path, rel_data_path, node_name_to_ports, logfile_prefix, env_variables): +def start_databases( + pg_path, rel_data_path, node_name_to_ports, logfile_prefix, env_variables +): def start(node_name): abs_data_path = os.path.abspath(os.path.join(rel_data_path, node_name)) node_port = node_name_to_ports[node_name] @@ -248,7 +250,12 @@ def logfile_name(logfile_prefix, node_name): def stop_databases( - pg_path, rel_data_path, node_name_to_ports, logfile_prefix, no_output=False, parallel=True + pg_path, + rel_data_path, + node_name_to_ports, + logfile_prefix, + no_output=False, + parallel=True, ): def stop(node_name): abs_data_path = os.path.abspath(os.path.join(rel_data_path, node_name)) @@ -287,7 +294,9 @@ def initialize_citus_cluster(bindir, datadir, settings, config): initialize_db_for_cluster( bindir, datadir, settings, config.node_name_to_ports.keys() ) - start_databases(bindir, datadir, config.node_name_to_ports, config.name, config.env_variables) + start_databases( + bindir, datadir, config.node_name_to_ports, config.name, config.env_variables + ) create_citus_extension(bindir, config.node_name_to_ports.values()) add_workers(bindir, config.worker_ports, config.coordinator_port()) if not config.is_mx: @@ -296,6 +305,7 @@ def initialize_citus_cluster(bindir, datadir, settings, config): add_coordinator_to_metadata(bindir, config.coordinator_port()) config.setup_steps() + def eprint(*args, **kwargs): """eprint prints to stderr""" diff --git a/src/test/regress/citus_tests/config.py b/src/test/regress/citus_tests/config.py index 40de2a3b6..88b47be45 100644 --- a/src/test/regress/citus_tests/config.py +++ b/src/test/regress/citus_tests/config.py @@ -57,8 +57,9 @@ port_lock = threading.Lock() def should_include_config(class_name): - - if inspect.isclass(class_name) and issubclass(class_name, CitusDefaultClusterConfig): + if inspect.isclass(class_name) and issubclass( + class_name, CitusDefaultClusterConfig + ): return True return False @@ -167,7 +168,9 @@ class CitusDefaultClusterConfig(CitusBaseClusterConfig): self.add_coordinator_to_metadata = True self.skip_tests = [ # Alter Table statement cannot be run from an arbitrary node so this test will fail - "arbitrary_configs_alter_table_add_constraint_without_name_create", "arbitrary_configs_alter_table_add_constraint_without_name"] + "arbitrary_configs_alter_table_add_constraint_without_name_create", + "arbitrary_configs_alter_table_add_constraint_without_name", + ] class CitusUpgradeConfig(CitusBaseClusterConfig): @@ -190,9 +193,13 @@ class PostgresConfig(CitusDefaultClusterConfig): self.new_settings = { "citus.use_citus_managed_tables": False, } - self.skip_tests = ["nested_execution", + self.skip_tests = [ + "nested_execution", # Alter Table statement cannot be run from an arbitrary node so this test will fail - "arbitrary_configs_alter_table_add_constraint_without_name_create", "arbitrary_configs_alter_table_add_constraint_without_name"] + "arbitrary_configs_alter_table_add_constraint_without_name_create", + "arbitrary_configs_alter_table_add_constraint_without_name", + ] + class CitusSingleNodeClusterConfig(CitusDefaultClusterConfig): def __init__(self, arguments): @@ -229,7 +236,7 @@ class CitusSmallSharedPoolSizeConfig(CitusDefaultClusterConfig): def __init__(self, arguments): super().__init__(arguments) self.new_settings = { - "citus.local_shared_pool_size": 5, + "citus.local_shared_pool_size": 5, "citus.max_shared_pool_size": 5, } @@ -275,7 +282,7 @@ class CitusUnusualExecutorConfig(CitusDefaultClusterConfig): # this setting does not necessarily need to be here # could go any other test - self.env_variables = {'PGAPPNAME' : 'test_app'} + self.env_variables = {"PGAPPNAME": "test_app"} class CitusSmallCopyBuffersConfig(CitusDefaultClusterConfig): @@ -307,9 +314,13 @@ class CitusUnusualQuerySettingsConfig(CitusDefaultClusterConfig): # requires the table with the fk to be converted to a citus_local_table. # As of c11, there is no way to do that through remote execution so this test # will fail - "arbitrary_configs_truncate_cascade_create", "arbitrary_configs_truncate_cascade", - # Alter Table statement cannot be run from an arbitrary node so this test will fail - "arbitrary_configs_alter_table_add_constraint_without_name_create", "arbitrary_configs_alter_table_add_constraint_without_name"] + "arbitrary_configs_truncate_cascade_create", + "arbitrary_configs_truncate_cascade", + # Alter Table statement cannot be run from an arbitrary node so this test will fail + "arbitrary_configs_alter_table_add_constraint_without_name_create", + "arbitrary_configs_alter_table_add_constraint_without_name", + ] + class CitusSingleNodeSingleShardClusterConfig(CitusDefaultClusterConfig): def __init__(self, arguments): @@ -328,15 +339,20 @@ class CitusShardReplicationFactorClusterConfig(CitusDefaultClusterConfig): self.skip_tests = [ # citus does not support foreign keys in distributed tables # when citus.shard_replication_factor >= 2 - "arbitrary_configs_truncate_partition_create", "arbitrary_configs_truncate_partition", + "arbitrary_configs_truncate_partition_create", + "arbitrary_configs_truncate_partition", # citus does not support modifying a partition when # citus.shard_replication_factor >= 2 - "arbitrary_configs_truncate_cascade_create", "arbitrary_configs_truncate_cascade", + "arbitrary_configs_truncate_cascade_create", + "arbitrary_configs_truncate_cascade", # citus does not support colocating functions with distributed tables when # citus.shard_replication_factor >= 2 - "function_create", "functions", - # Alter Table statement cannot be run from an arbitrary node so this test will fail - "arbitrary_configs_alter_table_add_constraint_without_name_create", "arbitrary_configs_alter_table_add_constraint_without_name"] + "function_create", + "functions", + # Alter Table statement cannot be run from an arbitrary node so this test will fail + "arbitrary_configs_alter_table_add_constraint_without_name_create", + "arbitrary_configs_alter_table_add_constraint_without_name", + ] class CitusSingleShardClusterConfig(CitusDefaultClusterConfig): diff --git a/src/test/regress/citus_tests/run_test.py b/src/test/regress/citus_tests/run_test.py index a2a7fad8f..aba11156e 100755 --- a/src/test/regress/citus_tests/run_test.py +++ b/src/test/regress/citus_tests/run_test.py @@ -12,23 +12,53 @@ import common import config args = argparse.ArgumentParser() -args.add_argument("test_name", help="Test name (must be included in a schedule.)", nargs='?') -args.add_argument("-p", "--path", required=False, help="Relative path for test file (must have a .sql or .spec extension)", type=pathlib.Path) +args.add_argument( + "test_name", help="Test name (must be included in a schedule.)", nargs="?" +) +args.add_argument( + "-p", + "--path", + required=False, + help="Relative path for test file (must have a .sql or .spec extension)", + type=pathlib.Path, +) args.add_argument("-r", "--repeat", help="Number of test to run", type=int, default=1) -args.add_argument("-b", "--use-base-schedule", required=False, help="Choose base-schedules rather than minimal-schedules", action='store_true') -args.add_argument("-w", "--use-whole-schedule-line", required=False, help="Use the whole line found in related schedule", action='store_true') -args.add_argument("--valgrind", required=False, help="Run the test with valgrind enabled", action='store_true') +args.add_argument( + "-b", + "--use-base-schedule", + required=False, + help="Choose base-schedules rather than minimal-schedules", + action="store_true", +) +args.add_argument( + "-w", + "--use-whole-schedule-line", + required=False, + help="Use the whole line found in related schedule", + action="store_true", +) +args.add_argument( + "--valgrind", + required=False, + help="Run the test with valgrind enabled", + action="store_true", +) args = vars(args.parse_args()) regress_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) -test_file_path = args['path'] -test_file_name = args['test_name'] -use_base_schedule = args['use_base_schedule'] -use_whole_schedule_line = args['use_whole_schedule_line'] +test_file_path = args["path"] +test_file_name = args["test_name"] +use_base_schedule = args["use_base_schedule"] +use_whole_schedule_line = args["use_whole_schedule_line"] -test_files_to_skip = ['multi_cluster_management', 'multi_extension', 'multi_test_helpers', 'multi_insert_select'] -test_files_to_run_without_schedule = ['single_node_enterprise'] +test_files_to_skip = [ + "multi_cluster_management", + "multi_extension", + "multi_test_helpers", + "multi_insert_select", +] +test_files_to_run_without_schedule = ["single_node_enterprise"] if not (test_file_name or test_file_path): print(f"FATAL: No test given.") @@ -36,7 +66,7 @@ if not (test_file_name or test_file_path): if test_file_path: - test_file_path = os.path.join(os.getcwd(), args['path']) + test_file_path = os.path.join(os.getcwd(), args["path"]) if not os.path.isfile(test_file_path): print(f"ERROR: test file '{test_file_path}' does not exist") @@ -45,7 +75,7 @@ if test_file_path: test_file_extension = pathlib.Path(test_file_path).suffix test_file_name = pathlib.Path(test_file_path).stem - if not test_file_extension in '.spec.sql': + if not test_file_extension in ".spec.sql": print( "ERROR: Unrecognized test extension. Valid extensions are: .sql and .spec" ) @@ -56,73 +86,73 @@ if test_file_name in test_files_to_skip: print(f"WARNING: Skipping exceptional test: '{test_file_name}'") sys.exit(0) -test_schedule = '' +test_schedule = "" # find related schedule for schedule_file_path in sorted(glob(os.path.join(regress_dir, "*_schedule"))): - for schedule_line in open(schedule_file_path, 'r'): - if re.search(r'\b' + test_file_name + r'\b', schedule_line): - test_schedule = pathlib.Path(schedule_file_path).stem - if use_whole_schedule_line: - test_schedule_line = schedule_line - else: - test_schedule_line = f"test: {test_file_name}\n" - break - else: - continue - break + for schedule_line in open(schedule_file_path, "r"): + if re.search(r"\b" + test_file_name + r"\b", schedule_line): + test_schedule = pathlib.Path(schedule_file_path).stem + if use_whole_schedule_line: + test_schedule_line = schedule_line + else: + test_schedule_line = f"test: {test_file_name}\n" + break + else: + continue + break # map suitable schedule if not test_schedule: - print( - f"WARNING: Could not find any schedule for '{test_file_name}'" - ) + print(f"WARNING: Could not find any schedule for '{test_file_name}'") sys.exit(0) elif "isolation" in test_schedule: - test_schedule = 'base_isolation_schedule' + test_schedule = "base_isolation_schedule" elif "failure" in test_schedule: - test_schedule = 'failure_base_schedule' + test_schedule = "failure_base_schedule" elif "enterprise" in test_schedule: - test_schedule = 'enterprise_minimal_schedule' + test_schedule = "enterprise_minimal_schedule" elif "split" in test_schedule: - test_schedule = 'minimal_schedule' + test_schedule = "minimal_schedule" elif "mx" in test_schedule: if use_base_schedule: - test_schedule = 'mx_base_schedule' + test_schedule = "mx_base_schedule" else: - test_schedule = 'mx_minimal_schedule' + test_schedule = "mx_minimal_schedule" elif "operations" in test_schedule: - test_schedule = 'minimal_schedule' + test_schedule = "minimal_schedule" elif test_schedule in config.ARBITRARY_SCHEDULE_NAMES: print(f"WARNING: Arbitrary config schedule ({test_schedule}) is not supported.") sys.exit(0) else: if use_base_schedule: - test_schedule = 'base_schedule' + test_schedule = "base_schedule" else: - test_schedule = 'minimal_schedule' + test_schedule = "minimal_schedule" # copy base schedule to a temp file and append test_schedule_line # to be able to run tests in parallel (if test_schedule_line is a parallel group.) -tmp_schedule_path = os.path.join(regress_dir, f"tmp_schedule_{ random.randint(1, 10000)}") +tmp_schedule_path = os.path.join( + regress_dir, f"tmp_schedule_{ random.randint(1, 10000)}" +) # some tests don't need a schedule to run # e.g tests that are in the first place in their own schedule if test_file_name not in test_files_to_run_without_schedule: shutil.copy2(os.path.join(regress_dir, test_schedule), tmp_schedule_path) with open(tmp_schedule_path, "a") as myfile: - for i in range(args['repeat']): - myfile.write(test_schedule_line) + for i in range(args["repeat"]): + myfile.write(test_schedule_line) # find suitable make recipe if "isolation" in test_schedule: - make_recipe = 'check-isolation-custom-schedule' + make_recipe = "check-isolation-custom-schedule" elif "failure" in test_schedule: - make_recipe = 'check-failure-custom-schedule' + make_recipe = "check-failure-custom-schedule" else: - make_recipe = 'check-custom-schedule' + make_recipe = "check-custom-schedule" -if args['valgrind']: - make_recipe += '-vg' +if args["valgrind"]: + make_recipe += "-vg" # prepare command to run tests test_command = f"make -C {regress_dir} {make_recipe} SCHEDULE='{pathlib.Path(tmp_schedule_path).stem}'" diff --git a/src/test/regress/citus_tests/upgrade/pg_upgrade_test.py b/src/test/regress/citus_tests/upgrade/pg_upgrade_test.py index 045ae42e1..b7fc75a08 100755 --- a/src/test/regress/citus_tests/upgrade/pg_upgrade_test.py +++ b/src/test/regress/citus_tests/upgrade/pg_upgrade_test.py @@ -112,7 +112,11 @@ def main(config): config.node_name_to_ports.keys(), ) common.start_databases( - config.new_bindir, config.new_datadir, config.node_name_to_ports, config.name, {} + config.new_bindir, + config.new_datadir, + config.node_name_to_ports, + config.name, + {}, ) citus_finish_pg_upgrade(config.new_bindir, config.node_name_to_ports.values()) diff --git a/src/test/regress/mitmscripts/fluent.py b/src/test/regress/mitmscripts/fluent.py index 2e6be9d34..363af680e 100644 --- a/src/test/regress/mitmscripts/fluent.py +++ b/src/test/regress/mitmscripts/fluent.py @@ -20,15 +20,17 @@ logging.basicConfig(format="%(asctime)s %(levelname)s %(message)s", level=loggin # I. Command Strings + class Handler: - ''' + """ This class hierarchy serves two purposes: 1. Allow command strings to be evaluated. Once evaluated you'll have a Handler you can pass packets to 2. Process packets as they come in and decide what to do with them. Subclasses which want to change how packets are handled should override _handle. - ''' + """ + def __init__(self, root=None): # all packets are first sent to the root handler to be processed self.root = root if root else self @@ -38,30 +40,31 @@ class Handler: def _accept(self, flow, message): result = self._handle(flow, message) - if result == 'pass': + if result == "pass": # defer to our child if not self.next: raise Exception("we don't know what to do!") - if self.next._accept(flow, message) == 'stop': + if self.next._accept(flow, message) == "stop": if self.root is not self: - return 'stop' + return "stop" self.next = KillHandler(self) flow.kill() else: return result def _handle(self, flow, message): - ''' + """ Handlers can return one of three things: - "done" tells the parent to stop processing. This performs the default action, which is to allow the packet to be sent. - "pass" means to delegate to self.next and do whatever it wants - "stop" means all processing will stop, and all connections will be killed - ''' + """ # subclasses must implement this raise NotImplementedError() + class FilterableMixin: def contains(self, pattern): self.next = Contains(self.root, pattern) @@ -76,7 +79,7 @@ class FilterableMixin: return self.next def __getattr__(self, attr): - ''' + """ Methods such as .onQuery trigger when a packet with that name is intercepted Adds support for commands such as: @@ -85,14 +88,17 @@ class FilterableMixin: Returns a function because the above command is resolved in two steps: conn.onQuery becomes conn.__getattr__("onQuery") conn.onQuery(query="COPY") becomes conn.__getattr__("onQuery")(query="COPY") - ''' - if attr.startswith('on'): + """ + if attr.startswith("on"): + def doit(**kwargs): self.next = OnPacket(self.root, attr[2:], kwargs) return self.next + return doit raise AttributeError + class ActionsMixin: def kill(self): self.next = KillHandler(self.root) @@ -118,31 +124,39 @@ class ActionsMixin: self.next = ConnectDelayHandler(self.root, timeMs) return self.next + class AcceptHandler(Handler): def __init__(self, root): super().__init__(root) + def _handle(self, flow, message): - return 'done' + return "done" + class KillHandler(Handler): def __init__(self, root): super().__init__(root) + def _handle(self, flow, message): flow.kill() - return 'done' + return "done" + class KillAllHandler(Handler): def __init__(self, root): super().__init__(root) + def _handle(self, flow, message): - return 'stop' + return "stop" + class ResetHandler(Handler): # try to force a RST to be sent, something went very wrong! def __init__(self, root): super().__init__(root) + def _handle(self, flow, message): - flow.kill() # tell mitmproxy this connection should be closed + flow.kill() # tell mitmproxy this connection should be closed # this is a mitmproxy.connections.ClientConnection(mitmproxy.tcp.BaseHandler) client_conn = flow.client_conn @@ -152,8 +166,9 @@ class ResetHandler(Handler): # cause linux to send a RST LINGER_ON, LINGER_TIMEOUT = 1, 0 conn.setsockopt( - socket.SOL_SOCKET, socket.SO_LINGER, - struct.pack('ii', LINGER_ON, LINGER_TIMEOUT) + socket.SOL_SOCKET, + socket.SO_LINGER, + struct.pack("ii", LINGER_ON, LINGER_TIMEOUT), ) conn.close() @@ -161,28 +176,35 @@ class ResetHandler(Handler): # tries to call conn.shutdown(), but there's nothing else to clean up so that's # maybe okay - return 'done' + return "done" + class CancelHandler(Handler): - 'Send a SIGINT to the process' + "Send a SIGINT to the process" + def __init__(self, root, pid): super().__init__(root) self.pid = pid + def _handle(self, flow, message): os.kill(self.pid, signal.SIGINT) # give the signal a chance to be received before we let the packet through time.sleep(0.1) - return 'done' + return "done" + class ConnectDelayHandler(Handler): - 'Delay the initial packet by sleeping before deciding what to do' + "Delay the initial packet by sleeping before deciding what to do" + def __init__(self, root, timeMs): super().__init__(root) self.timeMs = timeMs + def _handle(self, flow, message): if message.is_initial: - time.sleep(self.timeMs/1000.0) - return 'done' + time.sleep(self.timeMs / 1000.0) + return "done" + class Contains(Handler, ActionsMixin, FilterableMixin): def __init__(self, root, pattern): @@ -191,8 +213,9 @@ class Contains(Handler, ActionsMixin, FilterableMixin): def _handle(self, flow, message): if self.pattern in message.content: - return 'pass' - return 'done' + return "pass" + return "done" + class Matches(Handler, ActionsMixin, FilterableMixin): def __init__(self, root, pattern): @@ -201,47 +224,56 @@ class Matches(Handler, ActionsMixin, FilterableMixin): def _handle(self, flow, message): if self.pattern.search(message.content): - return 'pass' - return 'done' + return "pass" + return "done" + class After(Handler, ActionsMixin, FilterableMixin): "Don't pass execution to our child until we've handled 'times' messages" + def __init__(self, root, times): super().__init__(root) self.target = times def _handle(self, flow, message): - if not hasattr(flow, '_after_count'): + if not hasattr(flow, "_after_count"): flow._after_count = 0 if flow._after_count >= self.target: - return 'pass' + return "pass" flow._after_count += 1 - return 'done' + return "done" + class OnPacket(Handler, ActionsMixin, FilterableMixin): - '''Triggers when a packet of the specified kind comes around''' + """Triggers when a packet of the specified kind comes around""" + def __init__(self, root, packet_kind, kwargs): super().__init__(root) self.packet_kind = packet_kind self.filters = kwargs + def _handle(self, flow, message): if not message.parsed: # if this is the first message in the connection we just skip it - return 'done' + return "done" for msg in message.parsed: typ = structs.message_type(msg, from_frontend=message.from_client) if typ == self.packet_kind: - matches = structs.message_matches(msg, self.filters, message.from_client) + matches = structs.message_matches( + msg, self.filters, message.from_client + ) if matches: - return 'pass' - return 'done' + return "pass" + return "done" + class RootHandler(Handler, ActionsMixin, FilterableMixin): def _handle(self, flow, message): # do whatever the next Handler tells us to do - return 'pass' + return "pass" + class RecorderCommand: def __init__(self): @@ -250,23 +282,26 @@ class RecorderCommand: def dump(self): # When the user calls dump() we return everything we've captured - self.command = 'dump' + self.command = "dump" return self def reset(self): # If the user calls reset() we dump all captured packets without returning them - self.command = 'reset' + self.command = "reset" return self + # II. Utilities for interfacing with mitmproxy + def build_handler(spec): - 'Turns a command string into a RootHandler ready to accept packets' + "Turns a command string into a RootHandler ready to accept packets" root = RootHandler() recorder = RecorderCommand() - handler = eval(spec, {'__builtins__': {}}, {'conn': root, 'recorder': recorder}) + handler = eval(spec, {"__builtins__": {}}, {"conn": root, "recorder": recorder}) return handler.root + # a bunch of globals handler = None # the current handler used to process packets @@ -274,25 +309,25 @@ command_thread = None # sits on the fifo and waits for new commands to come in captured_messages = queue.Queue() # where we store messages used for recorder.dump() connection_count = count() # so we can give connections ids in recorder.dump() -def listen_for_commands(fifoname): +def listen_for_commands(fifoname): def emit_row(conn, from_client, message): # we're using the COPY text format. It requires us to escape backslashes - cleaned = message.replace('\\', '\\\\') - source = 'coordinator' if from_client else 'worker' - return '{}\t{}\t{}'.format(conn, source, cleaned) + cleaned = message.replace("\\", "\\\\") + source = "coordinator" if from_client else "worker" + return "{}\t{}\t{}".format(conn, source, cleaned) def emit_message(message): if message.is_initial: return emit_row( - message.connection_id, message.from_client, '[initial message]' + message.connection_id, message.from_client, "[initial message]" ) pretty = structs.print(message.parsed) return emit_row(message.connection_id, message.from_client, pretty) def all_items(queue_): - 'Pulls everything out of the queue without blocking' + "Pulls everything out of the queue without blocking" try: while True: yield queue_.get(block=False) @@ -300,23 +335,27 @@ def listen_for_commands(fifoname): pass def drop_terminate_messages(messages): - ''' + """ Terminate() messages happen eventually, Citus doesn't feel any need to send them immediately, so tests which embed them aren't reproducible and fail to timing issues. Here we simply drop those messages. - ''' + """ + def isTerminate(msg, from_client): kind = structs.message_type(msg, from_client) - return kind == 'Terminate' + return kind == "Terminate" for message in messages: if not message.parsed: yield message continue - message.parsed = ListContainer([ - msg for msg in message.parsed - if not isTerminate(msg, message.from_client) - ]) + message.parsed = ListContainer( + [ + msg + for msg in message.parsed + if not isTerminate(msg, message.from_client) + ] + ) message.parsed.from_frontend = message.from_client if len(message.parsed) == 0: continue @@ -324,35 +363,35 @@ def listen_for_commands(fifoname): def handle_recorder(recorder): global connection_count - result = '' + result = "" - if recorder.command == 'reset': - result = '' + if recorder.command == "reset": + result = "" connection_count = count() - elif recorder.command != 'dump': + elif recorder.command != "dump": # this should never happen - raise Exception('Unrecognized command: {}'.format(recorder.command)) + raise Exception("Unrecognized command: {}".format(recorder.command)) results = [] messages = all_items(captured_messages) messages = drop_terminate_messages(messages) for message in messages: - if recorder.command == 'reset': + if recorder.command == "reset": continue results.append(emit_message(message)) - result = '\n'.join(results) + result = "\n".join(results) - logging.debug('about to write to fifo') - with open(fifoname, mode='w') as fifo: - logging.debug('successfully opened the fifo for writing') - fifo.write('{}'.format(result)) + logging.debug("about to write to fifo") + with open(fifoname, mode="w") as fifo: + logging.debug("successfully opened the fifo for writing") + fifo.write("{}".format(result)) while True: - logging.debug('about to read from fifo') - with open(fifoname, mode='r') as fifo: - logging.debug('successfully opened the fifo for reading') + logging.debug("about to read from fifo") + with open(fifoname, mode="r") as fifo: + logging.debug("successfully opened the fifo for reading") slug = fifo.read() - logging.info('received new command: %s', slug.rstrip()) + logging.info("received new command: %s", slug.rstrip()) try: handler = build_handler(slug) @@ -371,13 +410,14 @@ def listen_for_commands(fifoname): except Exception as e: result = str(e) else: - result = '' + result = "" + + logging.debug("about to write to fifo") + with open(fifoname, mode="w") as fifo: + logging.debug("successfully opened the fifo for writing") + fifo.write("{}\n".format(result)) + logging.info("responded to command: %s", result.split("\n")[0]) - logging.debug('about to write to fifo') - with open(fifoname, mode='w') as fifo: - logging.debug('successfully opened the fifo for writing') - fifo.write('{}\n'.format(result)) - logging.info('responded to command: %s', result.split("\n")[0]) def create_thread(fifoname): global command_thread @@ -388,42 +428,46 @@ def create_thread(fifoname): return if command_thread: - print('cannot change the fifo path once mitmproxy has started'); + print("cannot change the fifo path once mitmproxy has started") return - command_thread = threading.Thread(target=listen_for_commands, args=(fifoname,), daemon=True) + command_thread = threading.Thread( + target=listen_for_commands, args=(fifoname,), daemon=True + ) command_thread.start() + # III. mitmproxy callbacks + def load(loader): - loader.add_option('slug', str, 'conn.allow()', "A script to run") - loader.add_option('fifo', str, '', "Which fifo to listen on for commands") + loader.add_option("slug", str, "conn.allow()", "A script to run") + loader.add_option("fifo", str, "", "Which fifo to listen on for commands") def configure(updated): global handler - if 'slug' in updated: + if "slug" in updated: text = ctx.options.slug handler = build_handler(text) - if 'fifo' in updated: + if "fifo" in updated: fifoname = ctx.options.fifo create_thread(fifoname) def tcp_message(flow: tcp.TCPFlow): - ''' + """ This callback is hit every time mitmproxy receives a packet. It's the main entrypoint into this script. - ''' + """ global connection_count tcp_msg = flow.messages[-1] # Keep track of all the different connections, assign a unique id to each - if not hasattr(flow, 'connection_id'): + if not hasattr(flow, "connection_id"): flow.connection_id = next(connection_count) tcp_msg.connection_id = flow.connection_id @@ -434,7 +478,9 @@ def tcp_message(flow: tcp.TCPFlow): # skip parsing initial messages for now, they're not important tcp_msg.parsed = None else: - tcp_msg.parsed = structs.parse(tcp_msg.content, from_frontend=tcp_msg.from_client) + tcp_msg.parsed = structs.parse( + tcp_msg.content, from_frontend=tcp_msg.from_client + ) # record the message, for debugging purposes captured_messages.put(tcp_msg) diff --git a/src/test/regress/mitmscripts/structs.py b/src/test/regress/mitmscripts/structs.py index a6333a3fa..10ef25bc3 100644 --- a/src/test/regress/mitmscripts/structs.py +++ b/src/test/regress/mitmscripts/structs.py @@ -1,8 +1,25 @@ from construct import ( Struct, - Int8ub, Int16ub, Int32ub, Int16sb, Int32sb, - Bytes, CString, Computed, Switch, Seek, this, Pointer, - GreedyRange, Enum, Byte, Probe, FixedSized, RestreamData, GreedyBytes, Array + Int8ub, + Int16ub, + Int32ub, + Int16sb, + Int32sb, + Bytes, + CString, + Computed, + Switch, + Seek, + this, + Pointer, + GreedyRange, + Enum, + Byte, + Probe, + FixedSized, + RestreamData, + GreedyBytes, + Array, ) import construct.lib as cl @@ -11,23 +28,30 @@ import re # For all possible message formats see: # https://www.postgresql.org/docs/current/protocol-message-formats.html + class MessageMeta(type): def __init__(cls, name, bases, namespace): - ''' + """ __init__ is called every time a subclass of MessageMeta is declared - ''' + """ if not hasattr(cls, "_msgtypes"): - raise Exception("classes which use MessageMeta must have a '_msgtypes' field") + raise Exception( + "classes which use MessageMeta must have a '_msgtypes' field" + ) if not hasattr(cls, "_classes"): - raise Exception("classes which use MessageMeta must have a '_classes' field") + raise Exception( + "classes which use MessageMeta must have a '_classes' field" + ) if not hasattr(cls, "struct"): # This is one of the direct subclasses return if cls.__name__ in cls._classes: - raise Exception("You've already made a class called {}".format( cls.__name__)) + raise Exception( + "You've already made a class called {}".format(cls.__name__) + ) cls._classes[cls.__name__] = cls # add a _type field to the struct so we can identify it while printing structs @@ -39,34 +63,41 @@ class MessageMeta(type): # register the type, so we can tell the parser about it key = cls.key if key in cls._msgtypes: - raise Exception('key {} is already assigned to {}'.format( - key, cls._msgtypes[key].__name__) + raise Exception( + "key {} is already assigned to {}".format( + key, cls._msgtypes[key].__name__ + ) ) cls._msgtypes[key] = cls + class Message: - 'Do not subclass this object directly. Instead, subclass of one of the below types' + "Do not subclass this object directly. Instead, subclass of one of the below types" def print(message): - 'Define this on subclasses you want to change the representation of' + "Define this on subclasses you want to change the representation of" raise NotImplementedError def typeof(message): - 'Define this on subclasses you want to change the expressed type of' + "Define this on subclasses you want to change the expressed type of" return message._type @classmethod def _default_print(cls, name, msg): recur = cls.print_message - return "{}({})".format(name, ",".join( - "{}={}".format(key, recur(value)) for key, value in msg.items() - if not key.startswith('_') - )) + return "{}({})".format( + name, + ",".join( + "{}={}".format(key, recur(value)) + for key, value in msg.items() + if not key.startswith("_") + ), + ) @classmethod def find_typeof(cls, msg): if not hasattr(cls, "_msgtypes"): - raise Exception('Do not call this method on Message, call it on a subclass') + raise Exception("Do not call this method on Message, call it on a subclass") if isinstance(msg, cl.ListContainer): raise ValueError("do not call this on a list of messages") if not isinstance(msg, cl.Container): @@ -80,7 +111,7 @@ class Message: @classmethod def print_message(cls, msg): if not hasattr(cls, "_msgtypes"): - raise Exception('Do not call this method on Message, call it on a subclass') + raise Exception("Do not call this method on Message, call it on a subclass") if isinstance(msg, cl.ListContainer): return repr([cls.print_message(message) for message in msg]) @@ -101,38 +132,34 @@ class Message: @classmethod def name_to_struct(cls): - return { - _class.__name__: _class.struct - for _class in cls._msgtypes.values() - } + return {_class.__name__: _class.struct for _class in cls._msgtypes.values()} @classmethod def name_to_key(cls): - return { - _class.__name__ : ord(key) - for key, _class in cls._msgtypes.items() - } + return {_class.__name__: ord(key) for key, _class in cls._msgtypes.items()} + class SharedMessage(Message, metaclass=MessageMeta): - 'A message which could be sent by either the frontend or the backend' + "A message which could be sent by either the frontend or the backend" _msgtypes = dict() _classes = dict() + class FrontendMessage(Message, metaclass=MessageMeta): - 'A message which will only be sent be a backend' + "A message which will only be sent be a backend" _msgtypes = dict() _classes = dict() + class BackendMessage(Message, metaclass=MessageMeta): - 'A message which will only be sent be a frontend' + "A message which will only be sent be a frontend" _msgtypes = dict() _classes = dict() + class Query(FrontendMessage): - key = 'Q' - struct = Struct( - "query" / CString("ascii") - ) + key = "Q" + struct = Struct("query" / CString("ascii")) @staticmethod def print(message): @@ -144,132 +171,151 @@ class Query(FrontendMessage): @staticmethod def normalize_shards(content): - ''' + """ For example: >>> normalize_shards( >>> 'COPY public.copy_test_120340 (key, value) FROM STDIN WITH (FORMAT BINARY))' >>> ) 'COPY public.copy_test_XXXXXX (key, value) FROM STDIN WITH (FORMAT BINARY))' - ''' + """ result = content - pattern = re.compile('public\.[a-z_]+(?P[0-9]+)') + pattern = re.compile("public\.[a-z_]+(?P[0-9]+)") for match in pattern.finditer(content): - span = match.span('shardid') - replacement = 'X'*( span[1] - span[0] ) - result = result[:span[0]] + replacement + result[span[1]:] + span = match.span("shardid") + replacement = "X" * (span[1] - span[0]) + result = result[: span[0]] + replacement + result[span[1] :] return result @staticmethod def normalize_timestamps(content): - ''' + """ For example: >>> normalize_timestamps('2018-06-07 05:18:19.388992-07') 'XXXX-XX-XX XX:XX:XX.XXXXXX-XX' >>> normalize_timestamps('2018-06-11 05:30:43.01382-07') 'XXXX-XX-XX XX:XX:XX.XXXXXX-XX' - ''' + """ pattern = re.compile( - '[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{2,6}-[0-9]{2}' + "[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{2,6}-[0-9]{2}" ) - return re.sub(pattern, 'XXXX-XX-XX XX:XX:XX.XXXXXX-XX', content) + return re.sub(pattern, "XXXX-XX-XX XX:XX:XX.XXXXXX-XX", content) @staticmethod def normalize_assign_txn_id(content): - ''' + """ For example: >>> normalize_assign_txn_id('SELECT assign_distributed_transaction_id(0, 52, ...') 'SELECT assign_distributed_transaction_id(0, XX, ...' - ''' + """ pattern = re.compile( - 'assign_distributed_transaction_id\s*\(' # a method call - '\s*[0-9]+\s*,' # an integer first parameter - '\s*(?P[0-9]+)' # an integer second parameter + "assign_distributed_transaction_id\s*\(" # a method call + "\s*[0-9]+\s*," # an integer first parameter + "\s*(?P[0-9]+)" # an integer second parameter ) result = content for match in pattern.finditer(content): - span = match.span('transaction_id') - result = result[:span[0]] + 'XX' + result[span[1]:] + span = match.span("transaction_id") + result = result[: span[0]] + "XX" + result[span[1] :] return result + class Terminate(FrontendMessage): - key = 'X' + key = "X" struct = Struct() + class CopyData(SharedMessage): - key = 'd' + key = "d" struct = Struct( - 'data' / GreedyBytes # reads all of the data left in this substream + "data" / GreedyBytes # reads all of the data left in this substream ) + class CopyDone(SharedMessage): - key = 'c' + key = "c" struct = Struct() + class EmptyQueryResponse(BackendMessage): - key = 'I' + key = "I" struct = Struct() + class CopyOutResponse(BackendMessage): - key = 'H' + key = "H" struct = Struct( "format" / Int8ub, "columncount" / Int16ub, - "columns" / Array(this.columncount, Struct( - "format" / Int16ub - )) + "columns" / Array(this.columncount, Struct("format" / Int16ub)), ) + class ReadyForQuery(BackendMessage): - key='Z' - struct = Struct("state"/Enum(Byte, - idle=ord('I'), - in_transaction_block=ord('T'), - in_failed_transaction_block=ord('E') - )) + key = "Z" + struct = Struct( + "state" + / Enum( + Byte, + idle=ord("I"), + in_transaction_block=ord("T"), + in_failed_transaction_block=ord("E"), + ) + ) + class CommandComplete(BackendMessage): - key = 'C' - struct = Struct( - "command" / CString("ascii") - ) + key = "C" + struct = Struct("command" / CString("ascii")) + class RowDescription(BackendMessage): - key = 'T' + key = "T" struct = Struct( "fieldcount" / Int16ub, - "fields" / Array(this.fieldcount, Struct( - "_type" / Computed("F"), - "name" / CString("ascii"), - "tableoid" / Int32ub, - "colattrnum" / Int16ub, - "typoid" / Int32ub, - "typlen" / Int16sb, - "typmod" / Int32sb, - "format_code" / Int16ub, - )) + "fields" + / Array( + this.fieldcount, + Struct( + "_type" / Computed("F"), + "name" / CString("ascii"), + "tableoid" / Int32ub, + "colattrnum" / Int16ub, + "typoid" / Int32ub, + "typlen" / Int16sb, + "typmod" / Int32sb, + "format_code" / Int16ub, + ), + ), ) + class DataRow(BackendMessage): - key = 'D' + key = "D" struct = Struct( "_type" / Computed("data_row"), "columncount" / Int16ub, - "columns" / Array(this.columncount, Struct( - "_type" / Computed("C"), - "length" / Int16sb, - "value" / Bytes(this.length) - )) + "columns" + / Array( + this.columncount, + Struct( + "_type" / Computed("C"), + "length" / Int16sb, + "value" / Bytes(this.length), + ), + ), ) + class AuthenticationOk(BackendMessage): - key = 'R' + key = "R" struct = Struct() + class ParameterStatus(BackendMessage): - key = 'S' + key = "S" struct = Struct( "name" / CString("ASCII"), "value" / CString("ASCII"), @@ -281,161 +327,156 @@ class ParameterStatus(BackendMessage): @staticmethod def normalize(name, value): - if name in ('TimeZone', 'server_version'): - value = 'XXX' + if name in ("TimeZone", "server_version"): + value = "XXX" return (name, value) + class BackendKeyData(BackendMessage): - key = 'K' - struct = Struct( - "pid" / Int32ub, - "key" / Bytes(4) - ) + key = "K" + struct = Struct("pid" / Int32ub, "key" / Bytes(4)) def print(message): # Both of these should be censored, for reproducible regression test output return "BackendKeyData(XXX)" + class NoticeResponse(BackendMessage): - key = 'N' + key = "N" struct = Struct( - "notices" / GreedyRange( + "notices" + / GreedyRange( Struct( - "key" / Enum(Byte, - severity=ord('S'), - _severity_not_localized=ord('V'), - _sql_state=ord('C'), - message=ord('M'), - detail=ord('D'), - hint=ord('H'), - _position=ord('P'), - _internal_position=ord('p'), - _internal_query=ord('q'), - _where=ord('W'), - schema_name=ord('s'), - table_name=ord('t'), - column_name=ord('c'), - data_type_name=ord('d'), - constraint_name=ord('n'), - _file_name=ord('F'), - _line_no=ord('L'), - _routine_name=ord('R') - ), - "value" / CString("ASCII") + "key" + / Enum( + Byte, + severity=ord("S"), + _severity_not_localized=ord("V"), + _sql_state=ord("C"), + message=ord("M"), + detail=ord("D"), + hint=ord("H"), + _position=ord("P"), + _internal_position=ord("p"), + _internal_query=ord("q"), + _where=ord("W"), + schema_name=ord("s"), + table_name=ord("t"), + column_name=ord("c"), + data_type_name=ord("d"), + constraint_name=ord("n"), + _file_name=ord("F"), + _line_no=ord("L"), + _routine_name=ord("R"), + ), + "value" / CString("ASCII"), ) ) ) def print(message): - return "NoticeResponse({})".format(", ".join( - "{}={}".format(response.key, response.value) - for response in message.notices - if not response.key.startswith('_') - )) + return "NoticeResponse({})".format( + ", ".join( + "{}={}".format(response.key, response.value) + for response in message.notices + if not response.key.startswith("_") + ) + ) + class Parse(FrontendMessage): - key = 'P' + key = "P" struct = Struct( "name" / CString("ASCII"), "query" / CString("ASCII"), "_parametercount" / Int16ub, - "parameters" / Array( - this._parametercount, - Int32ub - ) + "parameters" / Array(this._parametercount, Int32ub), ) + class ParseComplete(BackendMessage): - key = '1' + key = "1" struct = Struct() + class Bind(FrontendMessage): - key = 'B' + key = "B" struct = Struct( "destination_portal" / CString("ASCII"), "prepared_statement" / CString("ASCII"), "_parameter_format_code_count" / Int16ub, - "parameter_format_codes" / Array(this._parameter_format_code_count, - Int16ub), + "parameter_format_codes" / Array(this._parameter_format_code_count, Int16ub), "_parameter_value_count" / Int16ub, - "parameter_values" / Array( + "parameter_values" + / Array( this._parameter_value_count, - Struct( - "length" / Int32ub, - "value" / Bytes(this.length) - ) + Struct("length" / Int32ub, "value" / Bytes(this.length)), ), "result_column_format_count" / Int16ub, - "result_column_format_codes" / Array(this.result_column_format_count, - Int16ub) + "result_column_format_codes" / Array(this.result_column_format_count, Int16ub), ) + class BindComplete(BackendMessage): - key = '2' + key = "2" struct = Struct() + class NoData(BackendMessage): - key = 'n' + key = "n" struct = Struct() + class Describe(FrontendMessage): - key = 'D' + key = "D" struct = Struct( - "type" / Enum(Byte, - prepared_statement=ord('S'), - portal=ord('P') - ), - "name" / CString("ASCII") + "type" / Enum(Byte, prepared_statement=ord("S"), portal=ord("P")), + "name" / CString("ASCII"), ) def print(message): - return "Describe({}={})".format( - message.type, - message.name or "" - ) + return "Describe({}={})".format(message.type, message.name or "") + class Execute(FrontendMessage): - key = 'E' - struct = Struct( - "name" / CString("ASCII"), - "max_rows_to_return" / Int32ub - ) + key = "E" + struct = Struct("name" / CString("ASCII"), "max_rows_to_return" / Int32ub) def print(message): return "Execute({}, max_rows_to_return={})".format( - message.name or "", - message.max_rows_to_return + message.name or "", message.max_rows_to_return ) + class Sync(FrontendMessage): - key = 'S' + key = "S" struct = Struct() + frontend_switch = Switch( this.type, - { **FrontendMessage.name_to_struct(), **SharedMessage.name_to_struct() }, - default=Bytes(this.length - 4) + {**FrontendMessage.name_to_struct(), **SharedMessage.name_to_struct()}, + default=Bytes(this.length - 4), ) backend_switch = Switch( this.type, {**BackendMessage.name_to_struct(), **SharedMessage.name_to_struct()}, - default=Bytes(this.length - 4) + default=Bytes(this.length - 4), ) -frontend_msgtypes = Enum(Byte, **{ - **FrontendMessage.name_to_key(), - **SharedMessage.name_to_key() -}) +frontend_msgtypes = Enum( + Byte, **{**FrontendMessage.name_to_key(), **SharedMessage.name_to_key()} +) -backend_msgtypes = Enum(Byte, **{ - **BackendMessage.name_to_key(), - **SharedMessage.name_to_key() -}) +backend_msgtypes = Enum( + Byte, **{**BackendMessage.name_to_key(), **SharedMessage.name_to_key()} +) # It might seem a little circuitous to say a frontend message is a kind of frontend # message but this lets us easily customize how they're printed + class Frontend(FrontendMessage): struct = Struct( "type" / frontend_msgtypes, @@ -447,9 +488,7 @@ class Frontend(FrontendMessage): def print(message): if isinstance(message.body, bytes): - return "Frontend(type={},body={})".format( - chr(message.type), message.body - ) + return "Frontend(type={},body={})".format(chr(message.type), message.body) return FrontendMessage.print_message(message.body) def typeof(message): @@ -457,6 +496,7 @@ class Frontend(FrontendMessage): return "Unknown" return message.body._type + class Backend(BackendMessage): struct = Struct( "type" / backend_msgtypes, @@ -468,9 +508,7 @@ class Backend(BackendMessage): def print(message): if isinstance(message.body, bytes): - return "Backend(type={},body={})".format( - chr(message.type), message.body - ) + return "Backend(type={},body={})".format(chr(message.type), message.body) return BackendMessage.print_message(message.body) def typeof(message): @@ -478,10 +516,12 @@ class Backend(BackendMessage): return "Unknown" return message.body._type + # GreedyRange keeps reading messages until we hit EOF frontend_messages = GreedyRange(Frontend.struct) backend_messages = GreedyRange(Backend.struct) + def parse(message, from_frontend=True): if from_frontend: message = frontend_messages.parse(message) @@ -491,24 +531,27 @@ def parse(message, from_frontend=True): return message + def print(message): if message.from_frontend: return FrontendMessage.print_message(message) return BackendMessage.print_message(message) + def message_type(message, from_frontend): if from_frontend: return FrontendMessage.find_typeof(message) return BackendMessage.find_typeof(message) + def message_matches(message, filters, from_frontend): - ''' + """ Message is something like Backend(Query)) and fiters is something like query="COPY". For now we only support strings, and treat them like a regex, which is matched against the content of the wrapped message - ''' - if message._type != 'Backend' and message._type != 'Frontend': + """ + if message._type != "Backend" and message._type != "Frontend": raise ValueError("can't handle {}".format(message._type)) wrapped = message.body From 188cc7d2ae6b254c6557940e7b960126124d82e1 Mon Sep 17 00:00:00 2001 From: Jelte Fennema Date: Wed, 8 Feb 2023 11:35:29 +0100 Subject: [PATCH 3/7] Run python files through isort --- src/test/regress/bin/create_test.py | 4 +- src/test/regress/bin/diff-filter | 2 +- .../citus_arbitrary_configs.py | 12 +++-- src/test/regress/citus_tests/common.py | 8 +-- src/test/regress/citus_tests/config.py | 9 ++-- src/test/regress/citus_tests/run_test.py | 10 ++-- .../citus_tests/upgrade/citus_upgrade_test.py | 14 +++--- .../citus_tests/upgrade/pg_upgrade_test.py | 20 ++++---- src/test/regress/citus_tests/utils.py | 3 +- src/test/regress/mitmscripts/fluent.py | 13 +++-- src/test/regress/mitmscripts/structs.py | 50 +++++++++---------- 11 files changed, 74 insertions(+), 71 deletions(-) diff --git a/src/test/regress/bin/create_test.py b/src/test/regress/bin/create_test.py index 572d64510..c6efde8d8 100755 --- a/src/test/regress/bin/create_test.py +++ b/src/test/regress/bin/create_test.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 -import sys -import random import os +import random +import sys if len(sys.argv) != 2: print( diff --git a/src/test/regress/bin/diff-filter b/src/test/regress/bin/diff-filter index 3908ca37f..e0a1f0aa7 100755 --- a/src/test/regress/bin/diff-filter +++ b/src/test/regress/bin/diff-filter @@ -5,8 +5,8 @@ diff-filter denormalizes diff output by having lines beginning with ' ' or '+' come from file2's unmodified version. """ -from sys import argv, stdin, stdout import re +from sys import argv, stdin, stdout class FileScanner: diff --git a/src/test/regress/citus_tests/arbitrary_configs/citus_arbitrary_configs.py b/src/test/regress/citus_tests/arbitrary_configs/citus_arbitrary_configs.py index 2d07c0b63..1f509d479 100755 --- a/src/test/regress/citus_tests/arbitrary_configs/citus_arbitrary_configs.py +++ b/src/test/regress/citus_tests/arbitrary_configs/citus_arbitrary_configs.py @@ -12,21 +12,23 @@ Options: --seed= random number seed --base whether to use the base sql schedule or not """ +import os +import shutil import sys -import os, shutil # https://stackoverflow.com/questions/14132789/relative-imports-for-the-billionth-time/14132912#14132912 sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -import common -import config as cfg import concurrent.futures import multiprocessing -from docopt import docopt -import time import random +import time +import common +from docopt import docopt + +import config as cfg testResults = {} parallel_thread_amount = 1 diff --git a/src/test/regress/citus_tests/common.py b/src/test/regress/citus_tests/common.py index 1b8c69a45..d8fe9a8ee 100644 --- a/src/test/regress/citus_tests/common.py +++ b/src/test/regress/citus_tests/common.py @@ -1,9 +1,9 @@ -import os -import shutil -import sys -import subprocess import atexit import concurrent.futures +import os +import shutil +import subprocess +import sys import utils from utils import USER, cd diff --git a/src/test/regress/citus_tests/config.py b/src/test/regress/citus_tests/config.py index 88b47be45..0ee8a1707 100644 --- a/src/test/regress/citus_tests/config.py +++ b/src/test/regress/citus_tests/config.py @@ -1,11 +1,12 @@ -from os.path import expanduser +import inspect +import os import random import socket -from contextlib import closing -import os import threading +from contextlib import closing +from os.path import expanduser + import common -import inspect COORDINATOR_NAME = "coordinator" WORKER1 = "worker1" diff --git a/src/test/regress/citus_tests/run_test.py b/src/test/regress/citus_tests/run_test.py index aba11156e..55811f4f8 100755 --- a/src/test/regress/citus_tests/run_test.py +++ b/src/test/regress/citus_tests/run_test.py @@ -1,14 +1,16 @@ #!/usr/bin/env python3 -import sys +import argparse import os import pathlib -from glob import glob -import argparse -import shutil import random import re +import shutil +import sys +from glob import glob + import common + import config args = argparse.ArgumentParser() diff --git a/src/test/regress/citus_tests/upgrade/citus_upgrade_test.py b/src/test/regress/citus_tests/upgrade/citus_upgrade_test.py index 5aa253913..7e55bb714 100755 --- a/src/test/regress/citus_tests/upgrade/citus_upgrade_test.py +++ b/src/test/regress/citus_tests/upgrade/citus_upgrade_test.py @@ -13,32 +13,30 @@ Options: --mixed Run the verification phase with one node not upgraded. """ -import subprocess import atexit import os import re +import subprocess import sys # https://stackoverflow.com/questions/14132789/relative-imports-for-the-billionth-time/14132912#14132912 sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +import common import utils +from docopt import docopt from utils import USER -from docopt import docopt - from config import ( - CitusUpgradeConfig, - CITUS_VERSION_SQL, - MASTER_VERSION, AFTER_CITUS_UPGRADE_COORD_SCHEDULE, BEFORE_CITUS_UPGRADE_COORD_SCHEDULE, + CITUS_VERSION_SQL, + MASTER_VERSION, MIXED_AFTER_CITUS_UPGRADE_SCHEDULE, MIXED_BEFORE_CITUS_UPGRADE_SCHEDULE, + CitusUpgradeConfig, ) -import common - def main(config): install_citus(config.pre_tar_path) diff --git a/src/test/regress/citus_tests/upgrade/pg_upgrade_test.py b/src/test/regress/citus_tests/upgrade/pg_upgrade_test.py index b7fc75a08..dd7c990af 100755 --- a/src/test/regress/citus_tests/upgrade/pg_upgrade_test.py +++ b/src/test/regress/citus_tests/upgrade/pg_upgrade_test.py @@ -10,23 +10,25 @@ Options: --pgxsdir= Path to the PGXS directory(ex: ~/.pgenv/src/postgresql-11.3) """ -import sys, os +import os +import sys # https://stackoverflow.com/questions/14132789/relative-imports-for-the-billionth-time/14132912#14132912 sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -from config import ( - PGUpgradeConfig, - AFTER_PG_UPGRADE_SCHEDULE, - BEFORE_PG_UPGRADE_SCHEDULE, -) -from docopt import docopt -import utils -from utils import USER import atexit import subprocess import common +import utils +from docopt import docopt +from utils import USER + +from config import ( + AFTER_PG_UPGRADE_SCHEDULE, + BEFORE_PG_UPGRADE_SCHEDULE, + PGUpgradeConfig, +) def citus_prepare_pg_upgrade(pg_path, node_ports): diff --git a/src/test/regress/citus_tests/utils.py b/src/test/regress/citus_tests/utils.py index c9bfdc975..bbb0e3b83 100644 --- a/src/test/regress/citus_tests/utils.py +++ b/src/test/regress/citus_tests/utils.py @@ -1,6 +1,5 @@ -import subprocess import os - +import subprocess USER = "postgres" diff --git a/src/test/regress/mitmscripts/fluent.py b/src/test/regress/mitmscripts/fluent.py index 363af680e..f254d1bfa 100644 --- a/src/test/regress/mitmscripts/fluent.py +++ b/src/test/regress/mitmscripts/fluent.py @@ -1,20 +1,19 @@ -from collections import defaultdict -from itertools import count import logging -import re import os +import queue +import re import signal import socket import struct import threading import time import traceback -import queue - -from construct.lib import ListContainer -from mitmproxy import ctx, tcp +from collections import defaultdict +from itertools import count import structs +from construct.lib import ListContainer +from mitmproxy import ctx, tcp logging.basicConfig(format="%(asctime)s %(levelname)s %(message)s", level=logging.DEBUG) diff --git a/src/test/regress/mitmscripts/structs.py b/src/test/regress/mitmscripts/structs.py index 10ef25bc3..00208d23a 100644 --- a/src/test/regress/mitmscripts/structs.py +++ b/src/test/regress/mitmscripts/structs.py @@ -1,30 +1,30 @@ -from construct import ( - Struct, - Int8ub, - Int16ub, - Int32ub, - Int16sb, - Int32sb, - Bytes, - CString, - Computed, - Switch, - Seek, - this, - Pointer, - GreedyRange, - Enum, - Byte, - Probe, - FixedSized, - RestreamData, - GreedyBytes, - Array, -) -import construct.lib as cl - import re +import construct.lib as cl +from construct import ( + Array, + Byte, + Bytes, + Computed, + CString, + Enum, + FixedSized, + GreedyBytes, + GreedyRange, + Int8ub, + Int16sb, + Int16ub, + Int32sb, + Int32ub, + Pointer, + Probe, + RestreamData, + Seek, + Struct, + Switch, + this, +) + # For all possible message formats see: # https://www.postgresql.org/docs/current/protocol-message-formats.html From 9f41ea2157958cf5e4155e626c252079704f8c87 Mon Sep 17 00:00:00 2001 From: Jelte Fennema Date: Wed, 8 Feb 2023 11:55:57 +0100 Subject: [PATCH 4/7] Fix issues reported by flake8 --- src/test/regress/citus_tests/common.py | 9 ++++++--- src/test/regress/citus_tests/config.py | 2 +- src/test/regress/citus_tests/run_test.py | 6 +++--- .../citus_tests/upgrade/citus_upgrade_test.py | 2 +- src/test/regress/mitmscripts/fluent.py | 1 - src/test/regress/mitmscripts/structs.py | 12 ++++-------- 6 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/test/regress/citus_tests/common.py b/src/test/regress/citus_tests/common.py index d8fe9a8ee..1654905cb 100644 --- a/src/test/regress/citus_tests/common.py +++ b/src/test/regress/citus_tests/common.py @@ -6,7 +6,7 @@ import subprocess import sys import utils -from utils import USER, cd +from utils import USER def initialize_temp_dir(temp_dir): @@ -70,8 +70,11 @@ def add_settings(abs_data_path, settings): def create_role(pg_path, node_ports, user_name): def create(port): - command = "SET citus.enable_ddl_propagation TO OFF; SELECT worker_create_or_alter_role('{}', 'CREATE ROLE {} WITH LOGIN CREATEROLE CREATEDB;', NULL)".format( - user_name, user_name + command = ( + "SET citus.enable_ddl_propagation TO OFF;" + + "SELECT worker_create_or_alter_role('{}', 'CREATE ROLE {} WITH LOGIN CREATEROLE CREATEDB;', NULL)".format( + user_name, user_name + ) ) utils.psql(pg_path, port, command) command = "SET citus.enable_ddl_propagation TO OFF; GRANT CREATE ON DATABASE postgres to {}".format( diff --git a/src/test/regress/citus_tests/config.py b/src/test/regress/citus_tests/config.py index 0ee8a1707..7c9cd599a 100644 --- a/src/test/regress/citus_tests/config.py +++ b/src/test/regress/citus_tests/config.py @@ -75,7 +75,7 @@ def find_free_port(): port = next_port next_port += 1 return port - except: + except Exception: next_port += 1 # we couldn't find a port raise Exception("Couldn't find a port to use") diff --git a/src/test/regress/citus_tests/run_test.py b/src/test/regress/citus_tests/run_test.py index 55811f4f8..6ce58d480 100755 --- a/src/test/regress/citus_tests/run_test.py +++ b/src/test/regress/citus_tests/run_test.py @@ -63,7 +63,7 @@ test_files_to_skip = [ test_files_to_run_without_schedule = ["single_node_enterprise"] if not (test_file_name or test_file_path): - print(f"FATAL: No test given.") + print("FATAL: No test given.") sys.exit(2) @@ -77,7 +77,7 @@ if test_file_path: test_file_extension = pathlib.Path(test_file_path).suffix test_file_name = pathlib.Path(test_file_path).stem - if not test_file_extension in ".spec.sql": + if test_file_extension not in ".spec.sql": print( "ERROR: Unrecognized test extension. Valid extensions are: .sql and .spec" ) @@ -142,7 +142,7 @@ tmp_schedule_path = os.path.join( if test_file_name not in test_files_to_run_without_schedule: shutil.copy2(os.path.join(regress_dir, test_schedule), tmp_schedule_path) with open(tmp_schedule_path, "a") as myfile: - for i in range(args["repeat"]): + for _ in range(args["repeat"]): myfile.write(test_schedule_line) # find suitable make recipe diff --git a/src/test/regress/citus_tests/upgrade/citus_upgrade_test.py b/src/test/regress/citus_tests/upgrade/citus_upgrade_test.py index 7e55bb714..d36754d48 100755 --- a/src/test/regress/citus_tests/upgrade/citus_upgrade_test.py +++ b/src/test/regress/citus_tests/upgrade/citus_upgrade_test.py @@ -94,7 +94,7 @@ def remove_citus(tar_path): def remove_tar_files(tar_path): ps = subprocess.Popen(("tar", "tf", tar_path), stdout=subprocess.PIPE) - output = subprocess.check_output(("xargs", "rm", "-v"), stdin=ps.stdout) + subprocess.check_output(("xargs", "rm", "-v"), stdin=ps.stdout) ps.wait() diff --git a/src/test/regress/mitmscripts/fluent.py b/src/test/regress/mitmscripts/fluent.py index f254d1bfa..84ecbd193 100644 --- a/src/test/regress/mitmscripts/fluent.py +++ b/src/test/regress/mitmscripts/fluent.py @@ -8,7 +8,6 @@ import struct import threading import time import traceback -from collections import defaultdict from itertools import count import structs diff --git a/src/test/regress/mitmscripts/structs.py b/src/test/regress/mitmscripts/structs.py index 00208d23a..2be3a6a2e 100644 --- a/src/test/regress/mitmscripts/structs.py +++ b/src/test/regress/mitmscripts/structs.py @@ -8,7 +8,6 @@ from construct import ( Computed, CString, Enum, - FixedSized, GreedyBytes, GreedyRange, Int8ub, @@ -16,10 +15,7 @@ from construct import ( Int16ub, Int32sb, Int32ub, - Pointer, - Probe, RestreamData, - Seek, Struct, Switch, this, @@ -179,7 +175,7 @@ class Query(FrontendMessage): 'COPY public.copy_test_XXXXXX (key, value) FROM STDIN WITH (FORMAT BINARY))' """ result = content - pattern = re.compile("public\.[a-z_]+(?P[0-9]+)") + pattern = re.compile(r"public\.[a-z_]+(?P[0-9]+)") for match in pattern.finditer(content): span = match.span("shardid") replacement = "X" * (span[1] - span[0]) @@ -211,9 +207,9 @@ class Query(FrontendMessage): """ pattern = re.compile( - "assign_distributed_transaction_id\s*\(" # a method call - "\s*[0-9]+\s*," # an integer first parameter - "\s*(?P[0-9]+)" # an integer second parameter + r"assign_distributed_transaction_id\s*\(" # a method call + r"\s*[0-9]+\s*," # an integer first parameter + r"\s*(?P[0-9]+)" # an integer second parameter ) result = content for match in pattern.finditer(content): From 92dab9b4417013bae087d6bfd1f4409fe3602d88 Mon Sep 17 00:00:00 2001 From: Jelte Fennema Date: Wed, 8 Feb 2023 12:01:42 +0100 Subject: [PATCH 5/7] Add .venv to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 33f5549f3..368d568c8 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,4 @@ lib*.pc # style related temporary outputs *.uncrustify +.venv From 7bf3084b28721b08d8b9dac331196268dcc6bce8 Mon Sep 17 00:00:00 2001 From: Jelte Fennema Date: Wed, 8 Feb 2023 13:18:51 +0100 Subject: [PATCH 6/7] Add python tools to check-style and reindent make targets --- Makefile | 3 +++ ci/fix_style.sh | 2 ++ 2 files changed, 5 insertions(+) diff --git a/Makefile b/Makefile index 12b26cbbe..098b7c207 100644 --- a/Makefile +++ b/Makefile @@ -61,6 +61,9 @@ clean: clean-extension clean-pg_send_cancellation reindent: ${citus_abs_top_srcdir}/ci/fix_style.sh check-style: + black . --check --quiet + isort . --check --quiet + flake8 cd ${citus_abs_top_srcdir} && citus_indent --quiet --check .PHONY: reindent check-style diff --git a/ci/fix_style.sh b/ci/fix_style.sh index 69a8bbeb5..3d6e7ae83 100755 --- a/ci/fix_style.sh +++ b/ci/fix_style.sh @@ -9,6 +9,8 @@ cidir="${0%/*}" cd ${cidir}/.. citus_indent . --quiet +black . --quiet +isort . --quiet ci/editorconfig.sh ci/remove_useless_declarations.sh ci/disallow_c_comments_in_migrations.sh From b01d67c9430581b1aa0c7126e0cf77336f655847 Mon Sep 17 00:00:00 2001 From: Jelte Fennema Date: Wed, 8 Feb 2023 13:11:08 +0100 Subject: [PATCH 7/7] Add python code checks to CI --- .circleci/config.yml | 13 +++++++++++-- .flake8 | 4 ++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 501f00343..7601f276b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -6,7 +6,7 @@ orbs: parameters: image_suffix: type: string - default: '-v7e4468f' + default: '-v9a494cd' pg13_version: type: string default: '13.9' @@ -157,8 +157,17 @@ jobs: steps: - checkout - run: - name: 'Check Style' + name: 'Check C Style' command: citus_indent --check + - run: + name: 'Check Python style' + command: black --check . + - run: + name: 'Check Python import order' + command: isort --check . + - run: + name: 'Check Python lints' + command: flake8 . - run: name: 'Fix whitespace' command: ci/editorconfig.sh && git diff --exit-code diff --git a/.flake8 b/.flake8 index a52bd806a..112224148 100644 --- a/.flake8 +++ b/.flake8 @@ -1,6 +1,6 @@ [flake8] -# Whitespace before ':' (E203) is ignored for black -# Module level import not at top of file (E402) is ignored because of the way we do relative imports +# E203 is ignored for black +# E402 is ignored because of te way we do relative imports extend-ignore = E203, E402 # black will truncate to 88 characters usually, but long string literals it # might keep. That's fine in most cases unless it gets really excessive.