Merge current main fixes and validate craft-print rollout

This commit is contained in:
skylar taylor-barrick
2026-10-12 19:39:05 +02:00
51 changed files with 9553 additions and 164 deletions
+178
View File
@@ -0,0 +1,178 @@
71e1f5bf865131b5d9996e76a78372146283e5089e50f900ae927a6458b62101 PngSuite.LICENSE
a2853f2be68e460dcb9992819125b74503a01383e79f79a04b971ffa9d248544 PngSuite.README
6cf3bcd1757bfad2a7ce9c9659d4f609297a0828cafc7c9eddee18c5576ba9e9 PngSuite.png
9bf1c4610280efecc291361e2b7b026d602d03a7137f476ff84af626bb7b3a62 basi0g01.png
50ed4ff9c1c80578ae58ead6fb28ea2e44016b5b56a7b3e95c235ec86f97c0d8 basi0g02.png
0cce4212157dae7c0f71f5f72cef39cecab3347de455da0ace7de83eebd47849 basi0g04.png
b9300750e7414c9fed68cc7f4f531f5e2cdd2724f50142e8aa3a0070836bece9 basi0g08.png
d354026bdd0aa6ed7bedea7c92e5eef2ebbd4854848b8a0a48f562cc71bfe80f basi0g16.png
b2690d4475cdc39faf5a7d2de20de4e14eb96c6360fa791ec2003b4085ecacde basi2c08.png
ae13eba5fabbf7cbe8c01ec4458cdaa7e6bfdfcbd5109e831efaac9f39096027 basi2c16.png
8e1edfbc0382e0b545c70209271cedead2d530df1ce40f5296f0749226b36006 basi3p01.png
85f304eaaf6831b2bcc2c08892b947fef52f06337aaf23312d6326e367d73f77 basi3p02.png
78890ee2c4da71e460b5a1527efe7dc17d673ebe997f49ca1b4dcd7347d21e56 basi3p04.png
ca103eef7a7c50abc7af9d5ec0dc9fdc07668e2f030adbce226e050dcec9eea3 basi3p08.png
dfae01a4548e30519157bb51ce29d4de3b35b74288e9568b4d5911de01c6048b basi4a08.png
ef7df23ccd912309a4f89ca3c3094bf5ab55add25218e571e51cc6cb11cfad9e basi4a16.png
fe70ea20c8447e82abb7bc9dd05344a61a4631cc69e0974378df8f6263af0337 basi6a08.png
7fdd6bf08f04692bcf06b5c4262e7f76c49d8adb992789fd3f05aa0435645cd8 basi6a16.png
c8b1364d7771dd2f5a1b2d7d633abcf3f48dafee608558ecd2e5fc98f61894cd basn0g01.png
ca172e92d179c77b3d321ad501e452a59bdaafafde8ebd74fd0669c4eae58efc basn0g02.png
fb2d42c4c8f5c51590e675994a9b0c718fbf71c80e28142f231cfa7c5b0e26e8 basn0g04.png
268d061075d1dd2eeec62b31303d09f6998549e1bfb447a5f09c80a2b0978ac3 basn0g08.png
1a1fe155f40c11d79350b3f84c7eb2b29719037e57073cb7ae2ab9e8323f9360 basn0g16.png
c90e86090a625661b19960cafdde6e347d6e32d73837aaae533f66dd3f099506 basn2c08.png
1e1c28ec0ee5da6224404b4005076dd30e2335a1a80c0ca722046a245c4a2b3b basn2c16.png
c2d7cd682df5f74506b33a5d70c344aaee248fda79fdfef8e873426fd6f2b75b basn3p01.png
0466bb7ed9984cf03b70704564bcffab1df8ec0e8167473ba0f75e4fedce5a8f basn3p02.png
e1fc7be978d3149b98533d0076245ae64353b7967290f4204c1282ecb4ec1aba basn3p04.png
d58256cd2eb16b5740d4c1403d25ce43d8dd03e270627ab709d2fb141e3d904c basn3p08.png
7be24b618bfd437b921ae0caf9341d112338c6290cc62466deb19d2c7a512968 basn4a08.png
1c92ffd11d2fc89a36f170d0239668436407f5c8e3f8d93483fb3fc6bca361d7 basn4a16.png
559c594166eb156f461c9beff0f053196730dc998fdb0d2b801c89e6680860a5 basn6a08.png
8f9d81060aebf4576461403c5057de7f23f73157016b659402b906df805845aa basn6a16.png
dfae01a4548e30519157bb51ce29d4de3b35b74288e9568b4d5911de01c6048b bgai4a08.png
ef7df23ccd912309a4f89ca3c3094bf5ab55add25218e571e51cc6cb11cfad9e bgai4a16.png
559c594166eb156f461c9beff0f053196730dc998fdb0d2b801c89e6680860a5 bgan6a08.png
8f9d81060aebf4576461403c5057de7f23f73157016b659402b906df805845aa bgan6a16.png
71d041df5c8949ce07d5e93b3c9f752cefc75ef7326c49df05ae73915f1a755f bgbn4a08.png
c45de44d3442bd33072d68456541b10e0cc57c5d8bfc809208995d939091eb2d bggn4a16.png
2da3c3efa01bf166958852a104ece92daaede8ffb5e0452a55d40526258a8bf5 bgwn6a08.png
875bc2b44a4355551c51260def9664da68be1af8c14b82f76173aaf01ddb1929 bgyn6a16.png
c88909e74e039dd3df829bf24144b487171e53b17f5c5c07cbc08688247d24b5 ccwn2c08.png
dc365b39d49c287669d837872dd59aef30763a611dfc8c046c481e35e49a390a ccwn3p08.png
438018f19c85e582cb586ac7cca2220008ecb7fc70ce50e8f2a76b494c128a20 cdfn2c08.png
a068eaf1f7040490e08eda3259befb6689849dd0ff8bb4cc03c705d117cb2b9f cdhn2c08.png
ee08dfa303a3c09171280fc1821180dc0a4c3591af7e9077e8ce0435642c76d2 cdsn2c08.png
a849d89ba3d92146940d7c56d2fe74c9c3ec96725c7f24eefaf44160a6304de2 cdun2c08.png
587a8624228d3f605f475bd751a1f3a97f0bbab590c0ff518bce5da3abcb6f2b ch1n3p04.png
e3cace549047c6e5e693b9b135d53f7f9ca5b1088bf30cc408634598ba53758a ch2n3p08.png
79ccaf4abe5091e1f65f8a61496363ed3312ebb9ea1aaa98affadac3d98507de cm0n0g04.png
127bb50d224ac3ebff36160482a03b9e6686072e86c8ff4cd43e3ce2271b2315 cm7n0g04.png
774b384d0ad73698e7d93fc5a29976b332c619045e5966276c609b11459a4152 cm9n0g04.png
bc812de2b83abc9ae2a6d1ae01e91f9090214b9ed8fee1d56cdfea58f4f259d0 cs3n2c16.png
267104dced0858dd828cdd41edc0e87042c7094c77191565192a67045064b23f cs3n3p08.png
028fa49fbba3954c2eba27a4c3607196e87c1dae74192df3b3ad7afe5e0a3f0c cs5n2c08.png
a7a4021983d6b22685529cbafb65cd087c9e1af1c5d205c2e13e3efc4ae87b6a cs5n3p08.png
d90ddab7313e4e1fd7f20f16a6c546b449697fbcac1a7776298a74896f4662da cs8n2c08.png
5bffdb88e307a851f8e85b137a69c59e557a881db816e990061934c0155c894c cs8n3p08.png
081d1ec26b4157fbc032b76dc716321420f2d032a425de046557c7842766826d ct0n0g04.png
259116f8ecf849d83d824688eb02b4575aed11980f02ab6fb123281c25a6459d ct1n0g04.png
4583e63d1bdfa18b6abb47439dee9a73bed311ed1b0a71d22bed75fe4767832b cten0g04.png
d0607280c3539a8934c5cfd22788b382cdc31321cdedc75ae8a29e3948580ba6 ctfn0g04.png
5f4c87f5a8f589d029a908050880ac5d6aed3810e55e75444802d6d3fb27cf33 ctgn0g04.png
8ef799e59871755578e563c5b411bc3895c2f7112c115aaa921b6c04fe255bf2 cthn0g04.png
ca91332ecc04e5faac1f39ca4df4996b072a93f3e574e7f4b50d52aafbfe67b6 ctjn0g04.png
c0765e635a6423ec64f0314400c3df20745c4a4469ffcc323c7b00ba66b4aceb ctzn0g04.png
d04140d74bc60597c47b5aac371a3553d3add9354437bbd8c57007d94e197809 exif2c08.png
d055cc0bb505b37c6ecf88a808d71675eca5dd0ee683d70ebc9c0be0e1bb8e57 f00n0g08.png
0fe92b2aa2da04c885d1dbd85c834716f6cdd946364d97dcd597bb79d9e14427 f00n2c08.png
956ddaf133e6d9a8d36f1e00604d87fbc2d0295e933bb8caaef517e7d87e5342 f01n0g08.png
ef072ec6815ebf9b33e0553d2e4e4e7ed6911860a2512c67bcd10a9f0f09b9de f01n2c08.png
1f5b49c06ecc2a1756f0423b3de936bd18497866d035ac8eded78a6f17408a2c f02n0g08.png
ca4b937b3c587d5c007f193a2eec14dc96b0d23ff7d6aa9004e3badd1af9fe8f f02n2c08.png
d0563ae30c6ce7a01eae7a5cdc0ef780175bfa7653888ed93f17bef5fe4b2107 f03n0g08.png
2d101e3ef4f78a69437034671e93fe11faac0cfc4d44210dcca1b944caa886f7 f03n2c08.png
fec1a3c53833d0ddefa4486a98934c393cb4335b2dc31da70e6540a4686f1457 f04n0g08.png
c365c24153cb69fd3c162f00b296ae23a71a1595645d1aeb0ad23af680d7b4be f04n2c08.png
8ac0f095d2a943157e820fa121bccde08d5230af1b5830c3041d5f4da3524eba f99n0g04.png
3494b914dd1b094afa9a74a89bfa75219030e21af6fca3ed66c9d45032a047e9 g03n0g16.png
abce774b9624952c2d0c57ff681790ba620cf5616fe7e0ef0600600cdce3c22a g03n2c08.png
541389db0c72721c6c49501e1dbf6e32df41b4fcfa318676955e278f3f4332b3 g03n3p04.png
72350a2d9db2df09b626f1f1dd48061e9c749d471bf19814d2c5c10acbf007e5 g04n0g16.png
bfd3edfc4d85a43383556a51023533fa3888184aa53a6b43769910782beebfce g04n2c08.png
5ea85ebefb58b2ab113fc05bf1cb79ffb068359e3a5620843590a0a7b59bd49a g04n3p04.png
be19721d28e0b268af1e29e8190c952b94315bd08899383ae2c91e6dc8c4f889 g05n0g16.png
74ab5b8477992d65e220eeaff8df466522c1b392fa90c6f9935da5bdb7113c9d g05n2c08.png
2f46afd7ec15836b523cbce6965f34d89833b8d777afffaf0f53bce620d5f65e g05n3p04.png
9070f843981f765cc8e26a63751be5d58631d85a8a7b4e826358f80e8a679d03 g07n0g16.png
ca4cd32d222f65fb7beb7c9f40d8b6833b36d552b830ff1a1d67dae73b8ed0d8 g07n2c08.png
b8e7abeabcfd50b71cca78b6cdff7372310cfc179cd63304c3e904f469b333cb g07n3p04.png
a22486acb74ee5f947db3a20b1f314a9dd8aca3339840049ee559f4d676a6b5c g10n0g16.png
4ac23729aea109e8f6b1e831448115cf02dcd97559e9403847cbe6dd7e5c7347 g10n2c08.png
36cc2ea4b5b33cd18e0a01c7de85dbcaf7161d258cd0f2265a90cd835b15cf6d g10n3p04.png
1fae707d809296d2cf0d6aa653bead6199644fb1c859558ec7d2d69e709a423a g25n0g16.png
9b128cfa1bb417dd99251914d729062621fcea9f346168549b16a75fe030b0f6 g25n2c08.png
4197a0a25c4f74d42c2285d75b9fabe1eedbed45bc224ff1ec1784f5797e9dbf g25n3p04.png
1a1fe155f40c11d79350b3f84c7eb2b29719037e57073cb7ae2ab9e8323f9360 oi1n0g16.png
1e1c28ec0ee5da6224404b4005076dd30e2335a1a80c0ca722046a245c4a2b3b oi1n2c16.png
142bc4b1e9f8a9e3b0ba3d2c2025e485bf7e76df88f914bf8c9041b4562cc080 oi2n0g16.png
2c523f138e0ade1bed8f31d59ec0f34b82660b192d6af8b8f395f82b9ba9dabb oi2n2c16.png
aa2d77723ba44f654a41a2ab97e01271fe942fecd2f299c0a81da90acb2887ac oi4n0g16.png
661220a0ed03ca6d63ec8dcaac738d4a9fd089b491c40dd896267ffb6cf5015d oi4n2c16.png
ae70c9effd0d9957a0d42771bc987529a1f3715e068057fae63f0c7e50d5c53a oi9n0g16.png
fef98e9bebe6c4046c29f2568f7a735305c8661aa01f9c37a718135e5bc4f941 oi9n2c16.png
72406b6173ec20dd059e8cea594ef5bc8f81bac5e416e2b53058d2debd51d195 pp0n2c16.png
d5a6f964a601a5e1815525117c12c32b61e2aa09fd6040776d35f2f7115b7110 pp0n6a08.png
b321b9ac8f2aa641475b6936365954181726dcb8662dbf641284d82a83cc22a5 ps1n0g08.png
6c59d673032fe2d04dbd2ea91cc9bc7bebe7d5ab84911f00e36fb28994793576 ps1n2c16.png
bd2ae1b45fd771aaa8c57dccd183f3834fc9742f6dfc692606865049efb116e5 ps2n0g08.png
120c62ce9d014f7772c4b38a446991b5bbe6a22ef5ea59239d32e023bc167585 ps2n2c16.png
235eb82f1337e3fcefbdca566a53af3d3523db7588ba3cd7fa5dab19afe48a41 s01i3p01.png
6a4f6da9fe36d195219ad2beeac1a0423e2ee6de03dfb064a3b6fa31623eb230 s01n3p01.png
e7d26ab4b31e7066b0674a4374c093967a9f1761b96836aa3dbe624acd73ab52 s02i3p01.png
f4b324f2b1d013ee77f632e42129c7cd3eca55da846e775e744a1f224ad1efcd s02n3p01.png
4513dec80f54ffd04a28834b114254d6e6d66e52e32e0059174474f2fb15d6bc s03i3p01.png
4dee297bd7f747f87488509af11a1e1e0531fa67e6e8d0f8ab7f4df7940f0ff3 s03n3p01.png
918f94f3557e10de91efa680df507c8eafc347fa5023af43037701398128ad0f s04i3p01.png
e73c52f764b684ec5133cce11f7f18cc60065acdd308e2c3d871e0a792a1080e s04n3p01.png
77fe040e17167f9656afe46cf0031832e635745e4e7f19ba12dee32f5ec03d96 s05i3p02.png
1b9e244a5c1d6cc9830391fff4eac565ada026d000e60faf7525c8add6bdc726 s05n3p02.png
cf8d5fdb34c0afd38b1b4745bed6167771767ccdfe847c4d8766f7fcc5c41176 s06i3p02.png
5caabfc4b147fad5e49e558143faaab91888324240acf45e5db8fc49f7a7f667 s06n3p02.png
d9874bfad10353a9ea2876ee388c3e7f81f697ed141d0d79c5c2019d62a7a698 s07i3p02.png
e7d447394f5fa64c27353216af387352a77a1fb534922a2d974e60d965d4e414 s07n3p02.png
62adf855c6b1e3a007f9dcf3ce2ba244616c089269a511dd939e89a4b0853453 s08i3p02.png
544fdd476e566e8aef4c98604124ae05acef48be20d1aebb9b6674b08b2abbb6 s08n3p02.png
f2cf5b3109690704d106a0b22ea079e36064cc6077e1fb9a1ba87397d0bbcca1 s09i3p02.png
6be8f58b638e7f7114750f5796f66f650db8fb7242a36183850f02117f4eaa65 s09n3p02.png
a27e0dbf923ef868e7399560b76c23a50b2d8926cddd9775e2bc4e050273f205 s32i3p04.png
c80d9fc7ded0b7834ec11d0e324540c7be74016b61bc302a1d167218fa35fbf9 s32n3p04.png
cc6ad98dcf686d92f1165d951f90fac70e568cedb1fcbba33e8890d0e6cdad4d s33i3p04.png
998d08e084e3eea288984475bfed0d48ee2d8250e20b96755898b59152e36a34 s33n3p04.png
83eb2e82eb14ae3c608833ef371a6f472c4c6e63b7e9f4f92f5472f975793b97 s34i3p04.png
a25451168c0b03ab8f52dcc60c6b9c12bc5df5d55afabe1892b87d0a856bb4a0 s34n3p04.png
db5988c27ad577de1e494cb50229dc2772678531adde2db84493907eae003e54 s35i3p04.png
cd99fc4cc69bda1ff8e8697d8b423ae8d9e84a58ba1d2b480751953b6869e3b3 s35n3p04.png
a36b40927b730a31d60c7818fb4a32e67ace06393cdaa26edc09520b6cee066d s36i3p04.png
ebc08f6a1e0918c5d3a9a08e184c23019a3cf90d8843e4b71bf2ae16040b1a84 s36n3p04.png
565c79404c4b9e46c27edb2f93d7b26b93ec0a0481ab7a21fc405152a21f53ba s37i3p04.png
964078b1f3f472268b8ac97a8976b4d1cdb6a47e6243d67eddcb0205e65b9f61 s37n3p04.png
b107f120ffaffcb949ac4547e0f91555ac681f78c7e68c724e7ceddfd8e5387b s38i3p04.png
045b0f5ea35874741bf6cac40f2e63ec8726450ab241475df4b7e9303c39e339 s38n3p04.png
4b5a6d8c04c8c70e1de3bcb6f7b5cc3ba359b13e28de6f52e21a291d4bc8a937 s39i3p04.png
2115b6cad0140ea97fe6ec056b47307ef1e8b5c00db5f08707a0572b165e5f08 s39n3p04.png
48a3f19b5c3b2f7eca17fd34765a2a3e385d770f24c357bc66bd74417eb60a5a s40i3p04.png
c4598ea73b1ec015bef7dcfd7f67bc7bfc41546619953d1a92cd9b8657a7033a s40n3p04.png
c5e7eeaffa677bfac002ee968a0423e0fea3be7701d39ccd6a015df1d2d85e40 tbbn0g04.png
8fcfd031a5b716201854fdab25691feed6cd3ed7b713a51b94e1f43fc63fe0c1 tbbn2c16.png
8eb43a6020fda8ed7c8c9b88b974ef794ee8452db29212ae7e5c11003bfd76a3 tbbn3p08.png
49711ee271f96099521b83b75d58ed2f76d42ed46cb3c06656fde2cad697cf59 tbgn2c16.png
6f798628935a021fe446dce3d62861d1f7369aaf01bad10daa4917c5abccad7d tbgn3p08.png
8223d8b0c5f84b149f0c1f0e1f658346e07fcbd03ffc02b2e9eeea1481eb19bf tbrn2c08.png
01e86f108b244bdee4d72e384aced8a62369965ca2993ee6ef88f131c647f7d1 tbwn0g16.png
978b28cec43bd219517083b63661bb30a7b9cdf9fd4476ab4e70844565980d51 tbwn3p08.png
7b4eb64b4bd7eb36417752873ddaca027ce77a07f474b2af569f4eea1e984111 tbyn3p08.png
f05e317f64a038123faf96fdbd6fc3ab3eec14e1fc1eb87fd7da4d996be63526 tm3n3p02.png
a253e055d8cd94049cd95e9392e7aa60f3d5c922a8f0cccb84762b0c5f547545 tp0n0g08.png
5dced3fde56b950e61ce86f4ad54458336f66389ce8ca68524d4fd8ec546ff5b tp0n2c08.png
da40ddb18bd45db32ed03dc89e2250b096d808efe975a88477e4f5a91a4677c9 tp0n3p08.png
50bc927fe6cf1816454a7c27f8c743734be8db45e37e5ee3ede540f4e895e6c5 tp1n3p08.png
4059f7e6a1c5bac1801f70e09f9ec1e1297dcdce34055c13ab2703d6d9613c7e xc1n0g08.png
e252a0e7df3e794e52ce4a831edafef76e7043d0d8d84019db0f7fd0b30e20f4 xc9n2c08.png
3c0c2a68dd416a7be79400e0e2ceb358ba3191d5309d4bad9dd04b32ff6d5b60 xcrn0g04.png
71e4b2826f61556eda39f3a93c8769b14d3ac90f135177b9373061199dbef39a xcsn0g01.png
c1287690808e809dc5d4fb89d8a7fd69ed93521f290abd42021ca00a061a1ba4 xd0n2c08.png
00b53c3bbd0641454521b982bc6f6bcfda7c91f1874cefb3a9bac37d80a1a269 xd3n2c08.png
16e5b40fb2600db1af20fb79ff715c2869255e2d4bef20702f16534e5dd6a847 xd9n2c08.png
f9d1fb2a708703518368c392c74765a6e3e5b49dbb9717df3974452291032df9 xdtn0g01.png
318864720c8fc0dbe4884035f2183cc3bd3d92ec60d447879982942600e9fe2e xhdn0g08.png
968fd21abb8acf40fbca90676de3abcb3a6e35b01ba7a3b9190184eafb99c83d xlfn0g04.png
776701227c7094dd10b78f508430b8ea4f03471d072096382cbcca628dea1d2b xs1n0g01.png
9dd7e93ba9211f0caee09d8f12e37742936c469f45fcad21e577c9f0640cf99e xs2n0g01.png
166c8633d116d1c26631cb98f3a75340cf4385fa7ae6a67913bc78e709bcb30d xs4n0g01.png
7a380568beeac969908196ac89cd2347c96ac2f26fdc2b8e0314b62c59f0e308 xs7n0g01.png
e4767d3259e1b331c9ece9fb07fc30f385879ac12b3cd0e1cb923f3bfac81dcf z00n2c08.png
939abfc6e0a3e349bb5bcd8c4e427a6cdb2c7e0c23a773aefa1c4fc9f07e4cb4 z03n2c08.png
21652fe42f472acd83dcb207806add5737db0020b21d6b38b0a00be3e9e37990 z06n2c08.png
98d90a7e61aa1a9ac9d99af44dd25c70cd5a7b4a11a85da60f82923e18a78f9f z09n2c08.png
+514 -24
View File
@@ -1,10 +1,15 @@
//! `cargo xtask corpus [...]` (fetch test corpora) and `cargo xtask test-corpus [...]` (fetch,
//! then run the corpus tests). Pins: `corpus_pins.rs`. Docs: `docs/development.md` › Test corpora.
use std::path::PathBuf;
use std::process::Command;
use std::collections::BTreeMap;
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::process::{Child, Command, Stdio};
use std::sync::atomic::{AtomicU64, Ordering};
use crate::corpus_pins::{self, AG_PSD_COMMIT, HEIC_RS_COMMIT, OPENEXR_COMMIT, PHOTOCRAFT_CORPUS_COMMIT, PILLOW_HEIF_COMMIT, PNGSUITE_URL, PSD_TOOLS_COMMIT};
use crate::corpus_pins::{
self, AG_PSD_COMMIT, HEIC_RS_COMMIT, OPENEXR_COMMIT, PHOTOCRAFT_CORPUS_COMMIT, PILLOW_HEIF_COMMIT, PNGSUITE_SHA256, PNGSUITE_URL, PSD_TOOLS_COMMIT,
};
use crate::pinned::{PinnedCorpus, USER_AGENT};
use crate::{cargo, root, run};
@@ -119,7 +124,7 @@ fn status(present: bool) -> &'static str {
fn list() {
let corpus = root().join("corpus");
let png = corpus.join("pngsuite");
let png_ok = pngsuite_count(&png) > 0;
let png_ok = verify_pngsuite(&png, PNGSUITE_MANIFEST).is_ok();
println!(
"Test corpora live under {} (gitignored, never committed). The corpus tests are opt-in
(cargo feature `corpus`) and fail, not skip, when a corpus is missing. CI always runs them.
@@ -162,31 +167,212 @@ Pins: xtask/src/corpus_pins.rs. Moving one: change it, then --<name> --update-ma
);
}
fn pngsuite_count(dir: &std::path::Path) -> usize {
std::fs::read_dir(dir).map(|d| d.flatten().filter(|e| e.path().extension().is_some_and(|x| x == "png")).count()).unwrap_or(0)
const PNGSUITE_MANIFEST: &str = include_str!("../pngsuite.sha256");
const PNGSUITE_MAX_ARCHIVE_BYTES: u64 = 1024 * 1024;
const PNGSUITE_MAX_FILE_BYTES: u64 = 4 * 1024 * 1024;
static PNGSUITE_STAGE_ID: AtomicU64 = AtomicU64::new(0);
/// Do not trust cached filenames, symlinks, or a file's size remaining unchanged during a read.
fn pngsuite_bytes(path: &Path, limit: u64) -> Result<Vec<u8>, String> {
let meta = std::fs::symlink_metadata(path).map_err(|e| format!("{}: {e}", path.display()))?;
if !meta.is_file() || meta.len() > limit {
return Err(format!("{}: expected a regular file of at most {limit} bytes", path.display()));
}
let mut bytes = Vec::new();
std::fs::File::open(path)
.map_err(|e| format!("{}: {e}", path.display()))?
.take(limit + 1)
.read_to_end(&mut bytes)
.map_err(|e| format!("{}: {e}", path.display()))?;
if bytes.len() as u64 > limit {
return Err(format!("{}: exceeds {limit} bytes", path.display()));
}
Ok(bytes)
}
/// PngSuite: 175 PNGs from a fixed release archive (skipped when already present).
fn fetch_pngsuite() -> Result<(), String> {
let corpus = root().join("corpus");
fn verify_pngsuite(dir: &Path, manifest: &str) -> Result<(), String> {
let mut expected = BTreeMap::new();
for line in manifest.lines() {
let Some((hash, name)) = line.split_once(" ") else { return Err("pngsuite: invalid manifest line".into()) };
if hash.len() != 64
|| !hash.bytes().all(|c| c.is_ascii_hexdigit())
|| name.is_empty()
|| name.contains("..")
|| !name.bytes().all(|c| c.is_ascii_alphanumeric() || b"._-".contains(&c))
|| expected.insert(name, hash).is_some()
{
return Err("pngsuite: invalid manifest entry".into());
}
}
if expected.is_empty() {
return Err("pngsuite: empty manifest".into());
}
let meta = std::fs::symlink_metadata(dir).map_err(|e| format!("{}: {e}", dir.display()))?;
if !meta.is_dir() {
return Err(format!("{}: expected a regular directory", dir.display()));
}
let entries = std::fs::read_dir(dir).map_err(|e| format!("{}: {e}", dir.display()))?;
let mut seen = 0;
for entry in entries {
let entry = entry.map_err(|e| format!("{}: {e}", dir.display()))?;
let name = entry.file_name();
let hash = name.to_str().and_then(|n| expected.get(n)).ok_or_else(|| format!("pngsuite: unexpected file {}", entry.path().display()))?;
let bytes = pngsuite_bytes(&entry.path(), PNGSUITE_MAX_FILE_BYTES)?;
if crate::sha256::hex(&bytes) != **hash {
return Err(format!("pngsuite: {}: sha256 mismatch", entry.path().display()));
}
seen += 1;
}
if seen != expected.len() {
return Err(format!("pngsuite: missing files ({seen} of {})", expected.len()));
}
Ok(())
}
struct PngSuiteStage {
path: PathBuf,
keep: bool,
}
impl PngSuiteStage {
fn new(corpus: &Path) -> Result<Self, String> {
std::fs::create_dir_all(corpus).map_err(|e| format!("create {}: {e}", corpus.display()))?;
for _ in 0..100 {
let id = PNGSUITE_STAGE_ID.fetch_add(1, Ordering::Relaxed);
let path = corpus.join(format!(".pngsuite-{}-{id}", std::process::id()));
match std::fs::create_dir(&path) {
Ok(()) => return Ok(Self { path, keep: false }),
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {}
Err(e) => return Err(format!("create {}: {e}", path.display())),
}
}
Err("pngsuite: could not create a staging directory".into())
}
}
impl Drop for PngSuiteStage {
fn drop(&mut self) {
if !self.keep {
let _ = std::fs::remove_dir_all(&self.path);
}
}
}
/// Keep the old cache until the verified replacement is in place. On a failed rollback its
/// backup remains in staging and the error names it, rather than deleting the only copy.
fn install_pngsuite(
dest: &Path,
files: &Path,
backup: &Path,
backup_retained: &mut bool,
mut rename: impl FnMut(&Path, &Path) -> std::io::Result<()>,
) -> Result<(), String> {
let had_cache = match std::fs::symlink_metadata(dest) {
Ok(_) => true,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => false,
Err(e) => return Err(format!("{}: {e}", dest.display())),
};
if had_cache {
rename(dest, backup).map_err(|e| format!("pngsuite: preserve {}: {e}", dest.display()))?;
*backup_retained = true;
}
if let Err(e) = rename(files, dest) {
if had_cache && let Err(restore) = rename(backup, dest) {
return Err(format!("pngsuite: install failed ({e}); restore failed ({restore}); old cache retained at {}", backup.display()));
}
*backup_retained = false;
return Err(format!("pngsuite: install failed: {e}"));
}
*backup_retained = false;
Ok(())
}
fn fetch_pngsuite_in(
corpus: &Path,
archive_hash: &str,
manifest: &str,
download: impl FnOnce(&Path) -> Result<(), String>,
extract: impl FnOnce(&Path, &Path) -> Result<(), String>,
rename: impl FnMut(&Path, &Path) -> std::io::Result<()>,
) -> Result<(), String> {
let dest = corpus.join("pngsuite");
let have = pngsuite_count(&dest);
if have >= 175 {
println!("pngsuite: {have} PNG files in {} already", dest.display());
if verify_pngsuite(&dest, manifest).is_ok() {
println!("pngsuite: {} already matches its manifest", dest.display());
return Ok(());
}
std::fs::create_dir_all(&dest).map_err(|e| format!("create {}: {e}", dest.display()))?;
let tgz = corpus.join("PngSuite-2017jul19.tgz");
let mut curl = Command::new("curl");
curl.args(["-fsSL", "--retry", "3", "-A", USER_AGENT, "-o"]).arg(&tgz).arg(PNGSUITE_URL);
run(curl, &format!("curl {PNGSUITE_URL}"))?;
let mut tar = Command::new("tar");
tar.arg("-xzf").arg(&tgz).arg("-C").arg(&dest);
run(tar, "tar -xzf PngSuite-2017jul19.tgz")?;
let _ = std::fs::remove_file(&tgz);
let n = pngsuite_count(&dest);
println!("pngsuite: {n} PNG files in {}", dest.display());
if n == 0 { Err("no PNG files extracted".into()) } else { Ok(()) }
let mut stage = PngSuiteStage::new(corpus)?;
let archive = stage.path.join("archive.tgz");
download(&archive)?;
let bytes = pngsuite_bytes(&archive, PNGSUITE_MAX_ARCHIVE_BYTES)?;
if crate::sha256::hex(&bytes) != archive_hash {
return Err("pngsuite: archive sha256 mismatch; nothing extracted".into());
}
let files = stage.path.join("files");
std::fs::create_dir(&files).map_err(|e| format!("create {}: {e}", files.display()))?;
extract(&archive, &files)?;
verify_pngsuite(&files, manifest)?;
let backup = stage.path.join("previous");
install_pngsuite(&dest, &files, &backup, &mut stage.keep, rename)?;
println!("pngsuite: verified {}", dest.display());
Ok(())
}
/// Older curl versions cannot enforce --max-filesize for unknown-length responses. Write
/// only the allowed prefix, then probe for excess without spooling that byte to disk.
fn copy_pngsuite_download(reader: &mut impl Read, writer: &mut impl Write) -> std::io::Result<()> {
std::io::copy(&mut reader.take(PNGSUITE_MAX_ARCHIVE_BYTES), writer)?;
let mut extra = [0; 1];
loop {
match reader.read(&mut extra) {
Ok(0) => return Ok(()),
Ok(_) => return Err(std::io::Error::other(format!("pngsuite: archive exceeds {PNGSUITE_MAX_ARCHIVE_BYTES} bytes"))),
Err(e) if e.kind() == std::io::ErrorKind::Interrupted => {}
Err(e) => return Err(e),
}
}
}
fn finish_pngsuite_download(child: &mut Child, mut reader: impl Read, writer: &mut impl Write) -> Result<(), String> {
let result = copy_pngsuite_download(&mut reader, writer).map_err(|e| format!("pngsuite: download: {e}")).and_then(|()| {
let status = child.wait().map_err(|e| format!("pngsuite: wait for curl: {e}"))?;
if status.success() { Ok(()) } else { Err(format!("pngsuite: curl failed: {status}")) }
});
if result.is_err() {
let _ = child.kill();
let _ = child.wait();
}
result
}
fn download_pngsuite(mut curl: Command, archive: &Path) -> Result<(), String> {
let mut file = std::fs::File::create(archive).map_err(|e| format!("create {}: {e}", archive.display()))?;
let mut child = curl.stdout(Stdio::piped()).spawn().map_err(|e| format!("pngsuite: start curl: {e}"))?;
let Some(stdout) = child.stdout.take() else {
let _ = child.kill();
let _ = child.wait();
return Err("pngsuite: curl stdout unavailable".into());
};
finish_pngsuite_download(&mut child, stdout, &mut file)
}
/// PngSuite: fixed archive checked before tar; every cached file checked on each invocation.
fn fetch_pngsuite() -> Result<(), String> {
fetch_pngsuite_in(
&root().join("corpus"),
PNGSUITE_SHA256,
PNGSUITE_MANIFEST,
|archive| {
let mut curl = Command::new("curl");
curl.args(["-fsSL", "--retry", "3", "--max-filesize", &PNGSUITE_MAX_ARCHIVE_BYTES.to_string(), "-A", USER_AGENT, "-o", "-", PNGSUITE_URL]);
download_pngsuite(curl, archive)
},
|archive, files| {
let mut tar = Command::new("tar");
tar.arg("-xzf").arg(archive).arg("-C").arg(files);
run(tar, "tar -xzf PngSuite-2017jul19.tgz")
},
|from, to| std::fs::rename(from, to),
)
}
/// Files changed relative to `origin/main` (committed, staged or not).
@@ -286,4 +472,308 @@ mod tests {
fn all_without_update_manifest_only_verifies() {
assert!(calls(false, false).iter().all(|(_, u)| !*u));
}
const FIXTURE_FILES: &[(&str, &[u8])] = &[("a.png", b"alpha"), ("b.png", b"beta"), ("PngSuite.LICENSE", b"fixture licence")];
const FIXTURE_ARCHIVE: &[u8] = b"owned archive fixture";
struct Fixture(PathBuf);
impl Fixture {
fn new() -> Self {
let id = PNGSUITE_STAGE_ID.fetch_add(1, Ordering::Relaxed);
let path = std::env::temp_dir().join(format!("photocraft-pngsuite-test-{}-{id}", std::process::id()));
std::fs::create_dir(&path).unwrap();
Self(path)
}
fn cache(&self) -> PathBuf {
self.0.join("pngsuite")
}
fn old_cache(&self) {
std::fs::create_dir(self.cache()).unwrap();
std::fs::write(self.cache().join("old.png"), b"old cache bytes").unwrap();
}
fn assert_old_cache(&self) {
assert_eq!(std::fs::read(self.cache().join("old.png")).unwrap(), b"old cache bytes");
assert_eq!(std::fs::read_dir(&self.0).unwrap().count(), 1, "staging was not cleaned");
}
}
impl Drop for Fixture {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
fn fixture_manifest() -> String {
FIXTURE_FILES.iter().map(|(name, bytes)| format!("{} {name}\n", crate::sha256::hex(bytes))).collect()
}
fn fixture_extract(_: &Path, files: &Path) -> Result<(), String> {
for (name, bytes) in FIXTURE_FILES {
std::fs::write(files.join(name), bytes).map_err(|e| e.to_string())?;
}
Ok(())
}
fn fixture_download(archive: &Path) -> Result<(), String> {
std::fs::write(archive, FIXTURE_ARCHIVE).map_err(|e| e.to_string())
}
fn fixture_fetch(corpus: &Path) -> Result<(), String> {
fetch_pngsuite_in(corpus, &crate::sha256::hex(FIXTURE_ARCHIVE), &fixture_manifest(), fixture_download, fixture_extract, |from, to| {
std::fs::rename(from, to)
})
}
#[test]
fn pngsuite_cache_checks_bytes_names_and_regular_files() {
let f = Fixture::new();
std::fs::create_dir(f.cache()).unwrap();
fixture_extract(Path::new("unused"), &f.cache()).unwrap();
assert!(verify_pngsuite(&f.cache(), &fixture_manifest()).is_ok());
std::fs::write(f.cache().join("a.png"), b"changed").unwrap();
assert!(verify_pngsuite(&f.cache(), &fixture_manifest()).unwrap_err().contains("sha256 mismatch"));
fixture_extract(Path::new("unused"), &f.cache()).unwrap();
std::fs::remove_file(f.cache().join("a.png")).unwrap();
assert!(verify_pngsuite(&f.cache(), &fixture_manifest()).unwrap_err().contains("missing files"));
fixture_extract(Path::new("unused"), &f.cache()).unwrap();
std::fs::write(f.cache().join("extra.png"), b"extra").unwrap();
assert!(verify_pngsuite(&f.cache(), &fixture_manifest()).unwrap_err().contains("unexpected file"));
std::fs::remove_file(f.cache().join("extra.png")).unwrap();
std::fs::remove_file(f.cache().join("a.png")).unwrap();
std::fs::create_dir(f.cache().join("a.png")).unwrap();
assert!(verify_pngsuite(&f.cache(), &fixture_manifest()).unwrap_err().contains("regular file"));
}
#[test]
fn pngsuite_valid_cache_needs_no_download_or_extraction() {
let f = Fixture::new();
fixture_fetch(&f.0).unwrap();
fetch_pngsuite_in(
&f.0,
"unused",
&fixture_manifest(),
|_| panic!("valid cache downloaded"),
|_, _| panic!("valid cache extracted"),
|_, _| panic!("valid cache renamed"),
)
.unwrap();
}
#[test]
fn pngsuite_repairs_unverified_175_file_cache_and_removes_extras() {
let f = Fixture::new();
std::fs::create_dir(f.cache()).unwrap();
for i in 0..175 {
std::fs::write(f.cache().join(format!("fake-{i}.png")), b"not a PNG").unwrap();
}
fixture_fetch(&f.0).unwrap();
assert!(verify_pngsuite(&f.cache(), &fixture_manifest()).is_ok());
assert_eq!(std::fs::read_dir(f.cache()).unwrap().count(), FIXTURE_FILES.len());
assert_eq!(std::fs::read_dir(&f.0).unwrap().count(), 1);
}
#[test]
fn pngsuite_unexpected_or_truncated_archive_never_reaches_extractor() {
for bad in [b"unexpected".as_slice(), &FIXTURE_ARCHIVE[..4]] {
let f = Fixture::new();
f.old_cache();
let e = fetch_pngsuite_in(
&f.0,
&crate::sha256::hex(FIXTURE_ARCHIVE),
&fixture_manifest(),
|a| std::fs::write(a, bad).map_err(|e| e.to_string()),
|_, _| panic!("unverified archive reached extractor"),
|_, _| panic!("unverified archive reached installation"),
)
.unwrap_err();
assert!(e.contains("archive sha256 mismatch"));
f.assert_old_cache();
}
}
#[test]
fn pngsuite_download_or_extraction_failure_keeps_old_cache() {
let f = Fixture::new();
f.old_cache();
assert!(
fetch_pngsuite_in(
&f.0,
"unused",
&fixture_manifest(),
|_| Err("offline".into()),
|_, _| panic!("failed download extracted"),
|_, _| panic!("failed download installed")
)
.unwrap_err()
.contains("offline")
);
f.assert_old_cache();
assert!(
fetch_pngsuite_in(
&f.0,
&crate::sha256::hex(FIXTURE_ARCHIVE),
&fixture_manifest(),
fixture_download,
|_, files| {
std::fs::write(files.join("partial.png"), b"partial").unwrap();
Err("extract failed".into())
},
|_, _| panic!("failed extraction installed")
)
.unwrap_err()
.contains("extract failed")
);
f.assert_old_cache();
for extra in [false, true] {
let e = fetch_pngsuite_in(
&f.0,
&crate::sha256::hex(FIXTURE_ARCHIVE),
&fixture_manifest(),
fixture_download,
|a, files| {
fixture_extract(a, files)?;
if extra {
std::fs::write(files.join("extra.png"), b"extra").unwrap();
} else {
std::fs::remove_file(files.join("a.png")).unwrap();
}
Ok(())
},
|_, _| panic!("unverified extraction installed"),
)
.unwrap_err();
assert!(e.contains(if extra { "unexpected file" } else { "missing files" }));
f.assert_old_cache();
}
}
#[test]
fn pngsuite_failed_installation_restores_old_cache() {
for fail_backup in [false, true] {
let f = Fixture::new();
f.old_cache();
let e = fetch_pngsuite_in(&f.0, &crate::sha256::hex(FIXTURE_ARCHIVE), &fixture_manifest(), fixture_download, fixture_extract, |from, to| {
if (fail_backup && from == f.cache()) || (!fail_backup && from.file_name().is_some_and(|n| n == "files")) {
Err(std::io::Error::other("injected rename failure"))
} else {
std::fs::rename(from, to)
}
})
.unwrap_err();
assert!(e.contains("injected rename failure"));
f.assert_old_cache();
}
}
#[test]
fn pngsuite_failed_rollback_retains_and_names_the_backup() {
let f = Fixture::new();
f.old_cache();
let e = fetch_pngsuite_in(&f.0, &crate::sha256::hex(FIXTURE_ARCHIVE), &fixture_manifest(), fixture_download, fixture_extract, |from, to| {
if from == f.cache() { std::fs::rename(from, to) } else { Err(std::io::Error::other("injected install and rollback failure")) }
})
.unwrap_err();
let stage = std::fs::read_dir(&f.0).unwrap().next().unwrap().unwrap().path();
let backup = stage.join("previous");
assert!(e.contains(&backup.display().to_string()), "{e}");
assert_eq!(std::fs::read(backup.join("old.png")).unwrap(), b"old cache bytes");
std::fs::rename(backup, f.cache()).unwrap();
}
#[test]
fn pngsuite_reads_are_bounded_and_malformed_manifests_are_rejected() {
let f = Fixture::new();
let file = f.0.join("large");
std::fs::File::create(&file).unwrap().set_len(PNGSUITE_MAX_ARCHIVE_BYTES + 1).unwrap();
assert!(pngsuite_bytes(&file, PNGSUITE_MAX_ARCHIVE_BYTES).unwrap_err().contains("regular file"));
for malformed in ["", "invalid", "00 a.png", &format!("{} ../outside\n", crate::sha256::hex(b""))] {
assert!(verify_pngsuite(&f.0, malformed).is_err());
}
}
#[test]
fn pngsuite_download_copy_accepts_boundary_and_never_writes_excess() {
let limit = PNGSUITE_MAX_ARCHIVE_BYTES as usize;
for len in [limit - 1, limit, limit + 1, limit * 2] {
let data = vec![0xa5; len];
let mut out = Vec::new();
let result = copy_pngsuite_download(&mut std::io::Cursor::new(&data), &mut out);
assert_eq!(result.is_ok(), len <= limit);
assert_eq!(out, data[..len.min(limit)]);
}
}
// The ordinary test invocation returns immediately. Only this test's child command
// enables emission; it stays alive so cleanup must kill and reap that particular child.
#[test]
fn pngsuite_stream_fixture() {
if std::env::var_os("PHOTOCRAFT_PNGSUITE_STREAM_TEST").is_some() {
let mut out = std::io::stdout().lock();
for _ in 0..256 {
let _ = out.write_all(&[0xa5; 8192]);
}
std::thread::park();
}
}
fn stream_child() -> Child {
Command::new(std::env::current_exe().unwrap())
.args(["--exact", "corpus::tests::pngsuite_stream_fixture", "--nocapture"])
.env("PHOTOCRAFT_PNGSUITE_STREAM_TEST", "1")
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()
.unwrap()
}
#[test]
fn pngsuite_oversize_and_io_failures_kill_and_reap_only_the_download_child() {
struct FailedRead;
impl Read for FailedRead {
fn read(&mut self, _: &mut [u8]) -> std::io::Result<usize> {
Err(std::io::Error::other("injected read failure"))
}
}
struct FailedWrite;
impl Write for FailedWrite {
fn write(&mut self, _: &[u8]) -> std::io::Result<usize> {
Err(std::io::Error::other("injected write failure"))
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
let mut child = stream_child();
let stdout = child.stdout.take().unwrap();
let mut out = Vec::new();
assert!(finish_pngsuite_download(&mut child, stdout, &mut out).unwrap_err().contains("archive exceeds"));
assert_eq!(out.len() as u64, PNGSUITE_MAX_ARCHIVE_BYTES);
assert!(child.try_wait().unwrap().is_some());
let mut child = stream_child();
assert!(child.try_wait().unwrap().is_none());
assert!(finish_pngsuite_download(&mut child, FailedRead, &mut Vec::new()).unwrap_err().contains("injected read failure"));
assert!(child.try_wait().unwrap().is_some());
let mut child = stream_child();
let stdout = child.stdout.take().unwrap();
assert!(finish_pngsuite_download(&mut child, stdout, &mut FailedWrite).unwrap_err().contains("injected write failure"));
assert!(child.try_wait().unwrap().is_some());
}
#[cfg(unix)]
#[test]
fn pngsuite_cache_rejects_symlinks_even_when_target_bytes_match() {
let f = Fixture::new();
std::fs::create_dir(f.cache()).unwrap();
fixture_extract(Path::new("unused"), &f.cache()).unwrap();
std::fs::write(f.0.join("target"), b"alpha").unwrap();
std::fs::remove_file(f.cache().join("a.png")).unwrap();
std::os::unix::fs::symlink(f.0.join("target"), f.cache().join("a.png")).unwrap();
assert!(verify_pngsuite(&f.cache(), &fixture_manifest()).unwrap_err().contains("regular file"));
}
}
+4
View File
@@ -40,6 +40,10 @@ pub const ASSET_STORE_TEMPLATE_COMMIT: &str = "f671981ee89d15526285b3cc87b4393d3
pub const AFFINITY_SAMPLES_COMMIT: &str = "96aca6bd86bbe7a677f1cf0630bd832d23943be8";
/// PngSuite (public domain), a fixed release archive.
pub const PNGSUITE_URL: &str = "http://www.schaik.com/pngsuite/PngSuite-2017jul19.tgz";
/// Fixed archive, corroborated by HTTPS records in opencodecs at 6f070029 and rivet-png at
/// 134faad8 (corpus/checksums.json and tests/pngsuite/SHA256SUMS). Check the legacy HTTP
/// download against this committed digest before extraction.
pub const PNGSUITE_SHA256: &str = "0294b244c95a8342c01b00010cf34abdcabc7c6a34fd0fe1bd963917537bfdc8";
/// `corpus/photoshop`: 258 PSDs authored with Photoshop by the photocraft-corpus generator.
pub const PHOTOSHOP: PinnedCorpus = PinnedCorpus {
+2
View File
@@ -579,6 +579,8 @@ mod tests {
("el", "el.tsv"),
("uk", "uk.tsv"),
("nl", "nl.tsv"),
("hu", "hu.tsv"),
("tr", "tr.tsv"),
("vi", "vi.tsv"),
] {
let catalog_path = root.join("crates/ui-egui/src/i18n").join(path);