From d4b482f911bdb6384c1306632b4e156a4d5f5ff8 Mon Sep 17 00:00:00 2001 From: Ivan Barba Date: Thu, 17 Sep 2026 20:16:30 +0000 Subject: [PATCH 1/8] [Better Tests] Adds new test dependencies --- .gitignore | 3 + Pipfile | 3 + Pipfile.lock | 234 ++++++++++++++++-- .../tests/dev_env_testcontainers_test.py | 76 ++++++ 4 files changed, 292 insertions(+), 24 deletions(-) create mode 100644 src/clusterfuzz/tests/dev_env_testcontainers_test.py diff --git a/.gitignore b/.gitignore index 0ac5356f25e..7c2ba6a0a63 100644 --- a/.gitignore +++ b/.gitignore @@ -64,3 +64,6 @@ docker/base/Pipfile.lock tags cscope.files cscope.out + +# Test coverage files +.coverage \ No newline at end of file diff --git a/Pipfile b/Pipfile index 2fc1c4d9c27..52dc37ba3b3 100644 --- a/Pipfile +++ b/Pipfile @@ -27,8 +27,11 @@ pyfakefs = "==4.4.0" pylint = "~=2.4" pylint-protobuf = "==0.21.0" pyright = "==1.1.393" +pytest = "==9.1.1" +pytest-cov = "==7.1.0" pyyaml = "==6.0.2" selenium = "==3.141.0" +testcontainers = "==4.15.0" twine = "*" WebTest = "==3.0.0" yapf = "==0.22.0" diff --git a/Pipfile.lock b/Pipfile.lock index e10f232ff68..1316f3386f4 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "f8858cd09afd214bef4a14a6a3f0ad7046dcb715d65cb1a22ae92a62d1cc8c8f" + "sha256": "5806a07b5d297a510c279a9d5134d5add13ff1e248d887dd309e724314f05a48" }, "pipfile-spec": 6, "requires": {}, @@ -241,7 +241,6 @@ "sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9" ], "index": "pypi", - "markers": "python_version >= '3.6'", "version": "==37.0.4" }, "future": { @@ -249,7 +248,6 @@ "sha256:67045236dcfd6816dc439556d009594abf643e5eb48992e36beac09c2ca659b8" ], "index": "pypi", - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", "version": "==0.17.1" }, "google-api-core": { @@ -293,7 +291,6 @@ "sha256:c2cdc38b22fa2d3f6984db6368b9be11902235ef46e2252a17f8083fe3176160" ], "index": "pypi", - "markers": "python_version >= '3.7'", "version": "==2.3.4" }, "googleapis-common-protos": { @@ -402,7 +399,6 @@ "sha256:fee88269a090ada09ca63551bf2f573eb2424035bcf2cb1b121895b01a46594a" ], "index": "pypi", - "markers": "python_version >= '3.7'", "version": "==4.23.4" }, "psutil": { @@ -423,7 +419,6 @@ "sha256:fd8522436a6ada7b4aad6638662966de0d61d241cb821239b2ae7013d41a43d4" ], "index": "pypi", - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==5.9.4" }, "pyasn1": { @@ -489,6 +484,14 @@ "markers": "python_version >= '3.6' and python_version < '4'", "version": "==4.9.1" }, + "typing-extensions": { + "hashes": [ + "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", + "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5" + ], + "markers": "python_version >= '3.9'", + "version": "==4.16.0" + }, "urllib3": { "hashes": [ "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", @@ -512,7 +515,7 @@ "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34", "sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991" ], - "markers": "python_version >= '3.8'", + "markers": "python_version < '3.12'", "version": "==1.2.0" }, "bcrypt": { @@ -763,6 +766,136 @@ "markers": "python_version >= '3.7'", "version": "==3.4.3" }, + "coverage": { + "extras": [ + "toml" + ], + "hashes": [ + "sha256:070acb9da788dff743a4d36fc015feee12d68f0349959017542017c79f59c21c", + "sha256:0c309096926b119543dc16438a11ef4c80783d2f4e59ff94f7f462651a944cdc", + "sha256:0d0ececb32090e3fbb03e0d352b973a0485879b4de6c58daf47227b9988b99e5", + "sha256:166adae25b05b04c9a84135912066d9c97482115af38df1a419a38aacc6b6f5d", + "sha256:19a3ea2f364012ef06678118fffdc92442a16bef4a5c8ad4f4019dd8f9ac8876", + "sha256:1b24f79e25bcf6c73931aeca7a3dfc7595c0cb5e9364aba3fdf387a3de4b1c22", + "sha256:1ec9a4ee989c0d06ad95add0dbfdbb72b00ef53f43431ca0b612384e7878e5de", + "sha256:2270a794600b635ca9452ce4c32e2fe81a35f9caa17ffac0eba99f14f275bd4d", + "sha256:2959978f9d1d20a2c0c15d0a68baaeccf615ac1aa214cf4a05a10d6f568926c8", + "sha256:29c4d3e32a3b5efa420a3dc627c7e570deb80ef997def52c7686a474f5edc7ab", + "sha256:2b26b55b18e1a53e1a159dd743728c4ddbbef28ba19000a91aaff5ce023197ec", + "sha256:2b8256f8b525ba233d2e4cdcdce0d6673c66fc9bf70df1fd5e67c54a74e2d245", + "sha256:2c05913d0d5badf7ac83200f35dcf9514cce5df16a7cc89e7d1d7fff0461813b", + "sha256:3284754371dc78592aa3ae4d661d30ee20e02b2b6b0de3590a181a936d0d3b38", + "sha256:33300f2e140ccf26af3d8152e62bff71993f9310cfc63ba7a20940b0d246a0ae", + "sha256:3397b9032553d281ad6a9253b12675b65e0cc8cd7a3b0633cf48872c9eb13360", + "sha256:34bafe9f4094315248573e6223e11af0ec1b25f9cbca43bf0e9a26a189ba2751", + "sha256:35cbc81f937fc402971df45c897d2df2bfb2014efcd990360032aa0a651635da", + "sha256:38a7e16f061504ac2b45370bf5bf97e8250d8d3f25e37385bae884978166554b", + "sha256:3acd1d78397dead78dd1b011b5fc19cc823c190349acd549e63856dff649c80e", + "sha256:3d0a3681c12d3e0bcdea3d9414b04087828d6c1a482802d6f7f42c37ed530152", + "sha256:3d73bb1f85c4150ac208fb0755beb04b2e44897bad81414de9380f98dd74729f", + "sha256:3d8bd4e58b6a5c2018d808f297905393c6c61da466a48c3f0596a76a4900ebe4", + "sha256:3db3978211c3cead5437a80136ca0556bab8bc7828de15a762884b0598c41361", + "sha256:3df82f0a3cef4e1bcfc799436056f0b978dda319d0bfd4460c6e479b2802d98a", + "sha256:3f3b4469d3da3ecced775d1a8c9c5d9fc80f259e30b7b89f9fed0700d6035ecb", + "sha256:3f73ee3956fde2d461c9e2955dd48166e4821fc8587d135e8780fb84da2a098b", + "sha256:4027bf6d7bc0a16df058ce913b69f10c5687f8e1ca668f08caa659ce101744bf", + "sha256:4184e78a4465dcda359fb403172b8951dd220929cb0984c02fabca1742fff06f", + "sha256:46a88f51770df7c9bc376bd57d3f86cdc7624b8e16ac4b585a655c22b7a1b4db", + "sha256:46cd3a73e9140410de62cceb66214bce0e08fb3922b9176fbfc1522fec151b41", + "sha256:48a78a66fcce49d7f6156524bf979c0ac633d584199717c68c6ffa949fc14e6a", + "sha256:49c39c7068a494f8eb427155f5682f44feee43f9b3107fd54b1e52465379c54b", + "sha256:4b0359eb4c62f9993e176bc8f50450fc736a6b90dbcc05bb8584e948812699ae", + "sha256:4f12a9e27ca7b65e40a8475d27899b2d45064d9020e6a89148939e01987b5853", + "sha256:4f48b345f831eaf4402ab6333c2dc3e2e2b5bc7b9c1b8fe12680dee3f0538f01", + "sha256:528a61be40977c340cf201d23b69bd6a6bab507da60e9dbda85f8b30e935d70d", + "sha256:531d9be377fdcc05593b974656872eb82e808ebeb42a72515e3aaeb8bb7166f5", + "sha256:550a2a1faf7559f13d5344f12d1eb886ad87955155d7dfab2a3fe5c8ec8fe776", + "sha256:5539304fdbb2cc144df684d35a33b81145334d23e1c2367b5a923d25107f70b2", + "sha256:5597180ed7670cc94c04c65347418a467d3a43d5f0cf52fcac647f5425f42037", + "sha256:55eb268e5b81aefac759766c9162625b06c1bedb7b77d936225bafc4f038a6f6", + "sha256:64a2a5985d81810ed605ff0dc4ccd6555efcb5700353825532a9a0aea65826e1", + "sha256:65a8fc80898c9ce59f04349fe8b4849b1f9787f14e52ead990e5f849ff4727a0", + "sha256:6618f481053b63fc6121faf8fc676bd9b7163c2a19d9e984a2e850002c28ab57", + "sha256:66d70132b69b861805dc1ca46cdd733e54c416890e8f1371d2fd103f70b59c9c", + "sha256:681a9488c5a234397c4f013da065aa9e53eb7af4c78f1f80c6f15e7208acb855", + "sha256:684c7ee9b4c04358fe6ac8b517ab51ec35fcd79d08ff0f105dd8bcd96885bbb7", + "sha256:6b3fd0f3435ebb7a7183b32a6062a8b755f08242ced1f3f22761d30b56b3c2a5", + "sha256:6dd8dda3402a01a1a8fe8b753a282466f615128574a5590a9108acd07b1f8540", + "sha256:6fc735d6fe6d57f803e7ba021be4dde48e43e6aff94e6954350555d5332b0594", + "sha256:715dcb72c3280c428c3a20134b87e42c29acec9669136e899ab2de69ca86218d", + "sha256:72e013665e25cf9d44779f01f340af26319756f9a76822b7c94ce6b1d93813da", + "sha256:73a32694603a34ad01d7e51a481a4023410d8099e1d0757e067945695c10f0ae", + "sha256:7562f8067ed9360e8b9739e5703403a7686dd1b36bf0f89fc538047c54cdea90", + "sha256:756ba2d96d073c5a2a55d67fa22784763710fadbe22c41adde2d9cfa4dd78a8c", + "sha256:7580432cbe1e8b762660ae5806f04f869e1c02e519836a43f8094437e561e9f0", + "sha256:76491917771f179f9772efe218c5ccc65950dbdb35f4439298d8a8dfc6ec1f72", + "sha256:77890395cf37026a5907d3ad32376aa51f41c0f163b7477fdbd4f94966cc1d08", + "sha256:79afa9726438912e5cddd1fe541815cea9763c92935f594835e4c432565b68a9", + "sha256:7af03247d598a353bbbbe1b925deb735276e4d845e7197c4073dc89352b236fa", + "sha256:7e5727b2508f817f3126d6c33327dda32fe69d15514badfcd61db8bc4209ecef", + "sha256:802d1246c540e07486d4ee1adfa19a797e4b33529ffc371dc140644e8f27da0a", + "sha256:83362b64e215ef00b0ba33fcf13655ace6c9fdd144d5ad2ab59ac86c2daf166e", + "sha256:8643baeb590726c558b2faed6cd59b0917480f9367fcc692026f1a86d824fd08", + "sha256:8bb09a2d19b04db1fa0e087a7ca4f12458f7e0e7364cfcd838441d86fb1c61f6", + "sha256:8ee71a38c54bb2676bbe762b8b0943a79ccb1c2fd6a52054f66e63eda392f8c1", + "sha256:8f590d46c30d9e4c1fda3efefe5443f4c2f6a4192c5ca2ba403653e9ebadf097", + "sha256:8fae08e85b334ac6ac886002b5041396a31bcf805225bbe19847627203da99e2", + "sha256:946f58aa59b08bcd6afcc6a7bd0ff54ed5eee844f32ad69fe6814836d15856a2", + "sha256:961fc424e9d5229a99f8f1189942d8e7f4e1519147c3af64842f944aca03914d", + "sha256:996c2b891b441ec2b39725ee3e8386e2f11b4894b92be225fdfd54a3eeada2c8", + "sha256:99bf9ea435cefcefd220f8687c3ddbbf78dc2de0bd11b57c3ae9fbbdf8d5561a", + "sha256:9d8c54ec32e5c102b9241f75d88ae26538b53662868ca491736611db448d9c7a", + "sha256:a125fac1f6b1e88488d208a86b578e1790e3c4937f2e1568d23356141d236220", + "sha256:a337dc2d54c74430cd2febb8ee04f7c508ba8b3b412bf0463f077a66cfc73743", + "sha256:a4eff405b545dfcf79cf0d9d3ff750e5c5a887aa066114175193a81d249c5ee6", + "sha256:a6410b75fe07d5271eaa95fc24bd0a9177ed588d9d1c10c0cf67829adb8f0567", + "sha256:a9647a0ac46255b8fef59a433a2161f03e5483f3a35e1cbd9dfe4600baff0c6b", + "sha256:b0944dc3bee3091039bf970d73caaf930c906013128a421bdc132e797494d941", + "sha256:b10095528b866d322d33d6bf1709b7f8cbf959f12e8cb2ba22fc59c8717866b0", + "sha256:b44308854ef210b9b78df9cdfd4e159513382a859f5ef8464306d14a54c2a040", + "sha256:b7d4d7e6dcaf33e85f1919f03346403bdcc27437c420a78835f3805bca0ab71f", + "sha256:b7f2c26ce6ce0b1e0ca0d5fae96ea510e3a2e78b7207f06e76b7f2c87fa3d0af", + "sha256:b89d22a89d5bc05dd95b64e08295b8394aa96dc88e08f8ba210c9ebfebbe0489", + "sha256:bb462d59146656e278d1e8ed913ce374d0ba68a4e081acde1867f6d3377fc881", + "sha256:bc5354a124799f1f87b7637bbe6f18cd4bc66a1f37f6aa2b5db40f9adad531dc", + "sha256:bc53c3f3adaa939b7a063533ffe0ae1259e7073393c618043a99a6970a87e3df", + "sha256:c08ae35c1be2fe1ce4b4c628df5c6fc0dc9a87f8e5fe8e20238d249678984741", + "sha256:c389c6f9d1d518e1249ddcb8a7f158135644ce2c508fa6cc17b680777dad5bf2", + "sha256:c510dad19552d912058e4c3e3cbec3fb155dbe8d0ce0ceb7e7dbf5c5822bae0b", + "sha256:cb05c0ff98b56ba6969adf35556bc43bcb8d094df8bc9cb403acff53460c4e07", + "sha256:cc0b37fe6f5ce5f1ccc62ad4fa9b1ad201d8e9b6027fd5e0170877beee4b2d15", + "sha256:cdc57746c7ac0ea063351b4d651c3bb4dd4fd35e64dbb8e90c10e14eb03c4080", + "sha256:cf047bc39fde5425be2628666d0f435ed8817859111c3aacc84b32d858069f5d", + "sha256:d06dcc420b570bf683cdb647cc8fe62b672d9e429ef711c3cbbb7a6880ca1572", + "sha256:d0f02c633630e2b74522108ee95a84ad6e1204a8016a6cca5297f335ea27147e", + "sha256:d1039cb2de093225d597109342ce1675626bd127565e80b3044f4eca07c15b2e", + "sha256:d1ba5142d68dd2cb775cbd0ac8601819298152047803c8efe4eec6d7d7aa7878", + "sha256:d4ec944947de098ad5a1738413f9364689a57067ecbc328e9de37218aa1e5cc1", + "sha256:d57cc400275b9a2892e905fc893f732b21ddb95271bf96406c88e2f6367848b5", + "sha256:d7db888dd0a1df1a653cae7f99d4047430d2187a3626eda51bc847b0fd6b9b43", + "sha256:da506e669a8a851b59e122b4b219ea70996a6296f44f3a9348a852526ff961de", + "sha256:dac8b84c03e6029d272b8249c77018db83de59ca009a9adef7c144b4a62ee5e6", + "sha256:db651a9cf325a542bc2b7b8cc8f1b2bdc6492739bae3103731b2f1c85b96cff6", + "sha256:dccc142614d3419ed71857deb43f1d757829a4c7fce9994464b71e7e38309827", + "sha256:e306e98186b9cd109121f3583aeb7978797ad21d948f22944c5c08845cd554d0", + "sha256:e366587b370bc9b8b51b7b7272c610c56db5d5b4795b9e4a29d28ff2f440f809", + "sha256:e5eb1762e7eb5fad34ef913e8107c7788a66f19d328e598ce95bf7217f9e5c8f", + "sha256:e82e10b9d290f60b63459cfb245a841aec347603997206296b93881463a93dcf", + "sha256:ed5ade1bb18f62edace1bd198c66f9d4c75a8385d5fd24e87d917ea1a5958773", + "sha256:ee1d5fc9e3bd6a217906929cc97880239a91d20dae7746f538eb0eefee705ab1", + "sha256:ee5465db6e9152a7d09f3215309326878c6aa3ac509195a369f9d264ff4bfbd9", + "sha256:f0ba3892d81aacf36996c52f16bca04e39af31a6c5de930b7688ab617f4a6475", + "sha256:f2066c447fdd0bca39a9633a082d8ce67bf9a539a203b85059a364a405dc9fe9", + "sha256:f4aa0b0a6f81fa3deb211e643f6954e78b4376b62b9c218271236cfa757664e8", + "sha256:f83981779bcf9dfa06fa0a8d4cb43e0faec1706328ce07aa3e7b665b4ac0f210", + "sha256:fa02d561eb1d8d2f8ba43ba6e3cef4c6c402a3b632a9460fa329fcadcd5df6a3", + "sha256:fbbe8265736659a6be2e6042b6a35be13545d14b243cc1d7ecf65f90d788a370", + "sha256:fd8ac10cd2458b3c6343aac082fb9bd0e3fa806cb2c4975f2280153474b88412", + "sha256:fdb2f528b50953e29d22033b3256c396a700193c6e45b2490222ef9c333cbbf9" + ], + "markers": "python_version >= '3.10'", + "version": "==7.16.1" + }, "cryptography": { "hashes": [ "sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59", @@ -789,7 +922,6 @@ "sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9" ], "index": "pypi", - "markers": "python_version >= '3.6'", "version": "==37.0.4" }, "dill": { @@ -807,6 +939,14 @@ ], "version": "==0.4.0" }, + "docker": { + "hashes": [ + "sha256:a3f45fdeb9165e2d25d9a1d02ddf3bc70fb572cf5ebbf9b58558c22caf29b71f", + "sha256:cebb93773d334f778e023a7ee352a8d6e13ab1bd3b863a4d4a59dec897df43ac" + ], + "markers": "python_version >= '3.8'", + "version": "==7.2.0" + }, "docutils": { "hashes": [ "sha256:4ed966a0e96a0477d852f7af31bdcb3adc049fbb35ccba358c2ea8a03287615e", @@ -946,7 +1086,6 @@ "sha256:fa107460c842e4c1a6266150881694fefd4f33baa544ea9489601810c2210ef8" ], "index": "pypi", - "markers": "python_version >= '3.7'", "version": "==1.62.2" }, "gunicorn": { @@ -982,6 +1121,14 @@ "markers": "python_version >= '3.9'", "version": "==8.7.0" }, + "iniconfig": { + "hashes": [ + "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", + "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12" + ], + "markers": "python_version >= '3.10'", + "version": "==2.3.0" + }, "isort": { "hashes": [ "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109", @@ -1020,7 +1167,7 @@ "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732" ], - "markers": "python_version >= '3.7'", + "markers": "sys_platform == 'linux'", "version": "==0.9.0" }, "keyring": { @@ -1119,7 +1266,6 @@ "sha256:da33dfde7547ff57e5ba5564126cbfa114f14413b2fa50759b1fa5de1e4ab511" ], "index": "pypi", - "markers": "python_version >= '3.7'", "version": "==3.4.0" }, "nh3": { @@ -1160,7 +1306,6 @@ "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9" ], "index": "pypi", - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", "version": "==1.9.1" }, "packaging": { @@ -1177,7 +1322,6 @@ "sha256:7fc905272cefa4f364c1a3429cbbe9c0f98b793988efb5bf90aac80f08db09b1" ], "index": "pypi", - "markers": "python_version >= '3.7'", "version": "==0.9.0" }, "paramiko": { @@ -1194,7 +1338,6 @@ "sha256:e9333cdd365aeb9c3f9460ac2d84c4750e3ffa8e766fe3d5de03d4496b55d851" ], "index": "pypi", - "markers": "python_version >= '3.7'", "version": "==2022.8.5" }, "platformdirs": { @@ -1205,6 +1348,14 @@ "markers": "python_version >= '3.9'", "version": "==4.4.0" }, + "pluggy": { + "hashes": [ + "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", + "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746" + ], + "markers": "python_version >= '3.9'", + "version": "==1.6.0" + }, "protobuf": { "hashes": [ "sha256:0a5759f5696895de8cc913f084e27fd4125e8fb0914bb729a17816a33819f474", @@ -1222,7 +1373,6 @@ "sha256:fee88269a090ada09ca63551bf2f573eb2424035bcf2cb1b121895b01a46594a" ], "index": "pypi", - "markers": "python_version >= '3.7'", "version": "==4.23.4" }, "pycparser": { @@ -1239,7 +1389,6 @@ "sha256:1ac3b2845dabe69af56c20691b9347914581195ccdde352535fb7d4ff0055c19" ], "index": "pypi", - "markers": "python_version >= '3.5'", "version": "==4.4.0" }, "pygments": { @@ -1289,9 +1438,40 @@ "sha256:aeeb7ff4e0364775ef416a80111613f91a05c8e01e58ecfefc370ca0db7aed9c" ], "index": "pypi", - "markers": "python_version >= '3.7'", "version": "==1.1.393" }, + "pytest": { + "hashes": [ + "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", + "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c" + ], + "index": "pypi", + "version": "==9.1.1" + }, + "pytest-cov": { + "hashes": [ + "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", + "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678" + ], + "index": "pypi", + "version": "==7.1.0" + }, + "python-discovery": { + "hashes": [ + "sha256:6393b4eae1be8b2182670635e7baff89ac21cb9f8e86fd1ff40c7b1144febb4c", + "sha256:d4e244cf17b8b29819ed78003d55fbacf86eda23425b075454fff9271b79377a" + ], + "markers": "python_version >= '3.8'", + "version": "==1.6.0" + }, + "python-dotenv": { + "hashes": [ + "sha256:904552145e8bfed22162c09dab1c2b9b54fefa7b23ba780f4f26ca0316b0f0d9", + "sha256:a20a594dabeaa385725aa239d5244871c143ecb356add8a20fcf23773a6c3a35" + ], + "markers": "python_version >= '3.10'", + "version": "==1.2.3" + }, "pyyaml": { "hashes": [ "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff", @@ -1349,7 +1529,6 @@ "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4" ], "index": "pypi", - "markers": "python_version >= '3.8'", "version": "==6.0.2" }, "readme-renderer": { @@ -1424,6 +1603,14 @@ "markers": "python_version >= '3.9'", "version": "==2.8" }, + "testcontainers": { + "hashes": [ + "sha256:085cde086337632e19002719460b7b80bbab2bdd51bb3ea04f77d0de96504706", + "sha256:8796c14e76604031ad39cf0ed3b8e9806283a1fbf5270965c2b1c594caa31b74" + ], + "index": "pypi", + "version": "==4.15.0" + }, "tomlkit": { "hashes": [ "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", @@ -1451,11 +1638,11 @@ }, "typing-extensions": { "hashes": [ - "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", - "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548" + "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", + "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5" ], "markers": "python_version >= '3.9'", - "version": "==4.15.0" + "version": "==4.16.0" }, "urllib3": { "hashes": [ @@ -1486,7 +1673,7 @@ "sha256:682aaaf2af0c44ada4abfb70ded36393f0e307f4ab9456a215ce0020baefc31f", "sha256:c56d67fd6e87c2ee598b76abdd4e96cfad1f24cacdea5078d382b1f9d7b5ed2e" ], - "markers": "python_full_version >= '3.9.0'", + "markers": "python_version >= '3.9'", "version": "==3.0.2" }, "webob": { @@ -1494,7 +1681,7 @@ "sha256:45e34c58ed0c7e2ecd238ffd34432487ff13d9ad459ddfd77895e67abba7c1f9", "sha256:ad6078e2edb6766d1334ec3dee072ac6a7f95b1e32ce10def8ff7f0f02d56589" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.8.9" }, "webtest": { @@ -1503,7 +1690,6 @@ "sha256:54bd969725838d9861a9fa27f8d971f79d275d94ae255f5c501f53bb6d9929eb" ], "index": "pypi", - "markers": "python_version >= '3.6' and python_version < '4'", "version": "==3.0.0" }, "wrapt": { diff --git a/src/clusterfuzz/tests/dev_env_testcontainers_test.py b/src/clusterfuzz/tests/dev_env_testcontainers_test.py new file mode 100644 index 00000000000..0e7eb2a3a5a --- /dev/null +++ b/src/clusterfuzz/tests/dev_env_testcontainers_test.py @@ -0,0 +1,76 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Developer environment checks for the testcontainers dependency. + +These verify that a developer environment can actually launch containers +through testcontainers. They are skipped automatically when no usable Docker +daemon is reachable (e.g. on bots without Docker). + +Run with: + pipenv run python -m pytest \ + src/clusterfuzz/tests/dev_env_testcontainers_test.py + +or, from inside an activated virtualenv (`pipenv shell`): + python -m pytest src/clusterfuzz/tests/dev_env_testcontainers_test.py + +This is deliberately not part of CI: `butler.py py_unittest` only discovers +tests under src/clusterfuzz/_internal/tests/{core,appengine}, so this file is +never collected by the `core` or `appengine` targets that CI runs. +""" + +import pytest +from testcontainers.core.container import DockerContainer +from testcontainers.core.wait_strategies import LogMessageWaitStrategy + +IMAGE = 'alpine:3.20' +MESSAGE = 'hello from testcontainers' + + +def _docker_available(): + """Return True if a Docker daemon is reachable.""" + try: + # Imported lazily so that a missing docker SDK results in a skip, not an + # import error. + import docker # pylint: disable=import-outside-toplevel + docker.from_env().ping() + return True + except Exception: # pylint: disable=broad-except + return False + + +requires_docker = pytest.mark.skipif( + not _docker_available(), reason='No reachable Docker daemon.') + + +@requires_docker +def test_hello_world(): + """Verify testcontainers starts a container, waits for logs, and cleans up.""" + container = ( + DockerContainer(IMAGE).with_command(f'echo "{MESSAGE}"').waiting_for( + LogMessageWaitStrategy(MESSAGE).with_startup_timeout(60))) + with container: + stdout, _ = container.get_logs() + + assert MESSAGE in stdout.decode() + + +@requires_docker +def test_exec_in_running_container(): + """Verify exec on a live container returns its exit code and output.""" + with DockerContainer(IMAGE).with_command('sleep 30') as container: + assert container.get_wrapped_container().id + + exit_code, output = container.exec(['echo', MESSAGE]) + assert exit_code == 0 + assert MESSAGE in output.decode() From e2a18b7857826e30df72c7cba4350f1e061a142b Mon Sep 17 00:00:00 2001 From: Ivan Barba Date: Thu, 17 Sep 2026 20:34:52 +0000 Subject: [PATCH 2/8] [Better Tests] Renames file to avoid linting issues --- ...ntainers_test.py => test_dev_env_testcontainers.py} | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) rename src/clusterfuzz/tests/{dev_env_testcontainers_test.py => test_dev_env_testcontainers.py} (86%) diff --git a/src/clusterfuzz/tests/dev_env_testcontainers_test.py b/src/clusterfuzz/tests/test_dev_env_testcontainers.py similarity index 86% rename from src/clusterfuzz/tests/dev_env_testcontainers_test.py rename to src/clusterfuzz/tests/test_dev_env_testcontainers.py index 0e7eb2a3a5a..02cd460571b 100644 --- a/src/clusterfuzz/tests/dev_env_testcontainers_test.py +++ b/src/clusterfuzz/tests/test_dev_env_testcontainers.py @@ -19,14 +19,18 @@ Run with: pipenv run python -m pytest \ - src/clusterfuzz/tests/dev_env_testcontainers_test.py + src/clusterfuzz/tests/test_dev_env_testcontainers.py or, from inside an activated virtualenv (`pipenv shell`): - python -m pytest src/clusterfuzz/tests/dev_env_testcontainers_test.py + python -m pytest src/clusterfuzz/tests/test_dev_env_testcontainers.py This is deliberately not part of CI: `butler.py py_unittest` only discovers tests under src/clusterfuzz/_internal/tests/{core,appengine}, so this file is -never collected by the `core` or `appengine` targets that CI runs. +never collected by the `core` or `appengine` targets that CI runs. The +`test_` prefix (rather than a `_test.py` suffix) is also intentional: it keeps +pytest collection working while opting out of butler's lint rule requiring an +__init__.py next to every *_test.py file, which exists to protect unittest +discovery that this file does not rely on. """ import pytest From a5fc2070fdcdaedcfe10753de7c2adb329519aed Mon Sep 17 00:00:00 2001 From: Ivan Barba Date: Thu, 17 Sep 2026 20:43:19 +0000 Subject: [PATCH 3/8] [Better tests] Update copyright notice year --- src/clusterfuzz/tests/test_dev_env_testcontainers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clusterfuzz/tests/test_dev_env_testcontainers.py b/src/clusterfuzz/tests/test_dev_env_testcontainers.py index 02cd460571b..3c9f7b687fc 100644 --- a/src/clusterfuzz/tests/test_dev_env_testcontainers.py +++ b/src/clusterfuzz/tests/test_dev_env_testcontainers.py @@ -1,4 +1,4 @@ -# Copyright 2025 Google LLC +# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. From 320f4cecbf245609610aad4e78ec14cc7cce9363 Mon Sep 17 00:00:00 2001 From: Ivan Barba Date: Thu, 17 Sep 2026 21:18:11 +0000 Subject: [PATCH 4/8] [Better Tests] Nit: adds styling formatting in script doc --- src/clusterfuzz/tests/test_dev_env_testcontainers.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/clusterfuzz/tests/test_dev_env_testcontainers.py b/src/clusterfuzz/tests/test_dev_env_testcontainers.py index 3c9f7b687fc..a3a0b1753c2 100644 --- a/src/clusterfuzz/tests/test_dev_env_testcontainers.py +++ b/src/clusterfuzz/tests/test_dev_env_testcontainers.py @@ -18,11 +18,14 @@ daemon is reachable (e.g. on bots without Docker). Run with: +``` pipenv run python -m pytest \ src/clusterfuzz/tests/test_dev_env_testcontainers.py - +``` or, from inside an activated virtualenv (`pipenv shell`): +``` python -m pytest src/clusterfuzz/tests/test_dev_env_testcontainers.py +``` This is deliberately not part of CI: `butler.py py_unittest` only discovers tests under src/clusterfuzz/_internal/tests/{core,appengine}, so this file is From 5846ed0a1ae0e088e48d03ddb57102280c08b548 Mon Sep 17 00:00:00 2001 From: Ivan Barba Date: Tue, 22 Sep 2026 02:00:09 +0000 Subject: [PATCH 5/8] [Better Tests] Add wiremock 2.7.0 dev dependency --- Pipfile | 1 + Pipfile.lock | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 52dc37ba3b3..24783cbdbfa 100644 --- a/Pipfile +++ b/Pipfile @@ -35,3 +35,4 @@ testcontainers = "==4.15.0" twine = "*" WebTest = "==3.0.0" yapf = "==0.22.0" +wiremock = "==2.7.0" diff --git a/Pipfile.lock b/Pipfile.lock index 1316f3386f4..f199a36ccf3 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "5806a07b5d297a510c279a9d5134d5add13ff1e248d887dd309e724314f05a48" + "sha256": "53eaabf510edfb3ace3ad2d24ee36bfb731ba10286dcb1a2227a385cdb9114a3" }, "pipfile-spec": 6, "requires": {}, @@ -1121,6 +1121,14 @@ "markers": "python_version >= '3.9'", "version": "==8.7.0" }, + "importlib-resources": { + "hashes": [ + "sha256:82d5c6cca930697dbbd86c93333bb2c2e72861d4789a11c2662b933e5ad2b528", + "sha256:9f7bd0c97b79972a6cce36a366356d16d5e13b09679c11a58f1014bfdf8e64b2" + ], + "markers": "python_version >= '3.8'", + "version": "==5.13.0" + }, "iniconfig": { "hashes": [ "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", @@ -1692,6 +1700,14 @@ "index": "pypi", "version": "==3.0.0" }, + "wiremock": { + "hashes": [ + "sha256:11b3f5fd12dcede9084c6f0e900a08fd7c29c0d31b80d4c5d081671a52d46d55", + "sha256:124e8d4944fc42151d2a1fb99ea488cbcea388d20449ffdd3a267046e6c2c7ee" + ], + "index": "pypi", + "version": "==2.7.0" + }, "wrapt": { "hashes": [ "sha256:02b551d101f31694fc785e58e0720ef7d9a10c4e62c1c9358ce6f63f23e30a56", From 51aaf5157e28eee474628f1cdd0661dc171efefc Mon Sep 17 00:00:00 2001 From: Ivan Barba Date: Thu, 24 Sep 2026 04:03:44 +0000 Subject: [PATCH 6/8] [Better Tests] Renames test file --- .../tests/test_dev_env_testcontainers.py | 83 ------------------- 1 file changed, 83 deletions(-) delete mode 100644 src/clusterfuzz/tests/test_dev_env_testcontainers.py diff --git a/src/clusterfuzz/tests/test_dev_env_testcontainers.py b/src/clusterfuzz/tests/test_dev_env_testcontainers.py deleted file mode 100644 index a3a0b1753c2..00000000000 --- a/src/clusterfuzz/tests/test_dev_env_testcontainers.py +++ /dev/null @@ -1,83 +0,0 @@ -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Developer environment checks for the testcontainers dependency. - -These verify that a developer environment can actually launch containers -through testcontainers. They are skipped automatically when no usable Docker -daemon is reachable (e.g. on bots without Docker). - -Run with: -``` - pipenv run python -m pytest \ - src/clusterfuzz/tests/test_dev_env_testcontainers.py -``` -or, from inside an activated virtualenv (`pipenv shell`): -``` - python -m pytest src/clusterfuzz/tests/test_dev_env_testcontainers.py -``` - -This is deliberately not part of CI: `butler.py py_unittest` only discovers -tests under src/clusterfuzz/_internal/tests/{core,appengine}, so this file is -never collected by the `core` or `appengine` targets that CI runs. The -`test_` prefix (rather than a `_test.py` suffix) is also intentional: it keeps -pytest collection working while opting out of butler's lint rule requiring an -__init__.py next to every *_test.py file, which exists to protect unittest -discovery that this file does not rely on. -""" - -import pytest -from testcontainers.core.container import DockerContainer -from testcontainers.core.wait_strategies import LogMessageWaitStrategy - -IMAGE = 'alpine:3.20' -MESSAGE = 'hello from testcontainers' - - -def _docker_available(): - """Return True if a Docker daemon is reachable.""" - try: - # Imported lazily so that a missing docker SDK results in a skip, not an - # import error. - import docker # pylint: disable=import-outside-toplevel - docker.from_env().ping() - return True - except Exception: # pylint: disable=broad-except - return False - - -requires_docker = pytest.mark.skipif( - not _docker_available(), reason='No reachable Docker daemon.') - - -@requires_docker -def test_hello_world(): - """Verify testcontainers starts a container, waits for logs, and cleans up.""" - container = ( - DockerContainer(IMAGE).with_command(f'echo "{MESSAGE}"').waiting_for( - LogMessageWaitStrategy(MESSAGE).with_startup_timeout(60))) - with container: - stdout, _ = container.get_logs() - - assert MESSAGE in stdout.decode() - - -@requires_docker -def test_exec_in_running_container(): - """Verify exec on a live container returns its exit code and output.""" - with DockerContainer(IMAGE).with_command('sleep 30') as container: - assert container.get_wrapped_container().id - - exit_code, output = container.exec(['echo', MESSAGE]) - assert exit_code == 0 - assert MESSAGE in output.decode() From a4874a5503f59b59e6e56ef8e758daf68e8a8488 Mon Sep 17 00:00:00 2001 From: Ivan Barba Date: Thu, 24 Sep 2026 04:06:24 +0000 Subject: [PATCH 7/8] [Better Tests] Renames test file --- .../tests/test_local_env_testcontainers.py | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 src/clusterfuzz/tests/test_local_env_testcontainers.py diff --git a/src/clusterfuzz/tests/test_local_env_testcontainers.py b/src/clusterfuzz/tests/test_local_env_testcontainers.py new file mode 100644 index 00000000000..82cc5d6d3a2 --- /dev/null +++ b/src/clusterfuzz/tests/test_local_env_testcontainers.py @@ -0,0 +1,84 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Developer environment checks for the testcontainers dependency. + +These verify that a developer environment can actually launch containers +through testcontainers. They are skipped automatically when no usable Docker +daemon is reachable (e.g. on bots without Docker). + +Run with: +``` + pipenv run python -m pytest \ + src/clusterfuzz/tests/test_dev_env_testcontainers.py +``` +or, from inside an activated virtualenv (`pipenv shell`): +``` + python -m pytest src/clusterfuzz/tests/test_dev_env_testcontainers.py +``` + +This is deliberately not part of CI: `butler.py py_unittest` only discovers +tests under src/clusterfuzz/_internal/tests/{core,appengine}, so this file is +never collected by the `core` or `appengine` targets that CI runs. The +`test_` prefix (rather than a `_test.py` suffix) is also intentional: it keeps +pytest collection working while opting out of butler's lint rule requiring an +__init__.py next to every *_test.py file, which exists to protect unittest +discovery that this file does not rely on. +""" + +import pytest +from testcontainers.core.container import DockerContainer +from testcontainers.core.wait_strategies import LogMessageWaitStrategy + +IMAGE = 'alpine:3.20' +MESSAGE = 'hello from testcontainers' + + +def _docker_available(): + """Return True if a Docker daemon is reachable.""" + try: + # Imported lazily so that a missing docker SDK results in a skip, not an + # import error. + import docker # pylint: disable=import-outside-toplevel + docker.from_env().ping() + return True + except Exception as e: # pylint: disable=broad-except + print(f'Docker is not available: {e}') + return False + + +requires_docker = pytest.mark.skipif( + not _docker_available(), reason='No reachable Docker daemon.') + + +@requires_docker +def test_hello_world(): + """Verify testcontainers starts a container, waits for logs, and cleans up.""" + container = ( + DockerContainer(IMAGE).with_command(f'echo "{MESSAGE}"').waiting_for( + LogMessageWaitStrategy(MESSAGE).with_startup_timeout(60))) + with container: + stdout, _ = container.get_logs() + + assert MESSAGE in stdout.decode() + + +@requires_docker +def test_exec_in_running_container(): + """Verify exec on a live container returns its exit code and output.""" + with DockerContainer(IMAGE).with_command('sleep 30') as container: + assert container.get_wrapped_container().id + + exit_code, output = container.exec(['echo', MESSAGE]) + assert exit_code == 0 + assert MESSAGE in output.decode() From 33d4e7bc92ad7e96a5b418eb698022d4f9db74a7 Mon Sep 17 00:00:00 2001 From: Ivan Barba Date: Thu, 24 Sep 2026 17:04:17 +0000 Subject: [PATCH 8/8] [Better Tests] Deletes demonstration script, plus handles nit comments --- .gitignore | 2 +- .../tests/test_local_env_testcontainers.py | 84 ------------------- 2 files changed, 1 insertion(+), 85 deletions(-) delete mode 100644 src/clusterfuzz/tests/test_local_env_testcontainers.py diff --git a/.gitignore b/.gitignore index 7c2ba6a0a63..0de64eca22f 100644 --- a/.gitignore +++ b/.gitignore @@ -66,4 +66,4 @@ cscope.files cscope.out # Test coverage files -.coverage \ No newline at end of file +.coverage diff --git a/src/clusterfuzz/tests/test_local_env_testcontainers.py b/src/clusterfuzz/tests/test_local_env_testcontainers.py deleted file mode 100644 index 82cc5d6d3a2..00000000000 --- a/src/clusterfuzz/tests/test_local_env_testcontainers.py +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Developer environment checks for the testcontainers dependency. - -These verify that a developer environment can actually launch containers -through testcontainers. They are skipped automatically when no usable Docker -daemon is reachable (e.g. on bots without Docker). - -Run with: -``` - pipenv run python -m pytest \ - src/clusterfuzz/tests/test_dev_env_testcontainers.py -``` -or, from inside an activated virtualenv (`pipenv shell`): -``` - python -m pytest src/clusterfuzz/tests/test_dev_env_testcontainers.py -``` - -This is deliberately not part of CI: `butler.py py_unittest` only discovers -tests under src/clusterfuzz/_internal/tests/{core,appengine}, so this file is -never collected by the `core` or `appengine` targets that CI runs. The -`test_` prefix (rather than a `_test.py` suffix) is also intentional: it keeps -pytest collection working while opting out of butler's lint rule requiring an -__init__.py next to every *_test.py file, which exists to protect unittest -discovery that this file does not rely on. -""" - -import pytest -from testcontainers.core.container import DockerContainer -from testcontainers.core.wait_strategies import LogMessageWaitStrategy - -IMAGE = 'alpine:3.20' -MESSAGE = 'hello from testcontainers' - - -def _docker_available(): - """Return True if a Docker daemon is reachable.""" - try: - # Imported lazily so that a missing docker SDK results in a skip, not an - # import error. - import docker # pylint: disable=import-outside-toplevel - docker.from_env().ping() - return True - except Exception as e: # pylint: disable=broad-except - print(f'Docker is not available: {e}') - return False - - -requires_docker = pytest.mark.skipif( - not _docker_available(), reason='No reachable Docker daemon.') - - -@requires_docker -def test_hello_world(): - """Verify testcontainers starts a container, waits for logs, and cleans up.""" - container = ( - DockerContainer(IMAGE).with_command(f'echo "{MESSAGE}"').waiting_for( - LogMessageWaitStrategy(MESSAGE).with_startup_timeout(60))) - with container: - stdout, _ = container.get_logs() - - assert MESSAGE in stdout.decode() - - -@requires_docker -def test_exec_in_running_container(): - """Verify exec on a live container returns its exit code and output.""" - with DockerContainer(IMAGE).with_command('sleep 30') as container: - assert container.get_wrapped_container().id - - exit_code, output = container.exec(['echo', MESSAGE]) - assert exit_code == 0 - assert MESSAGE in output.decode()