##// END OF EJS Templates
branching: merge stable into default
Raphaël Gomès -
r51829:6408777c merge default
parent child Browse files
Show More
@@ -1,251 +1,252 b''
1 1 35fb62a3a673d5322f6274a44ba6456e5e4b3b37 0 iD8DBQBEYmO2ywK+sNU5EO8RAnaYAKCO7x15xUn5mnhqWNXqk/ehlhRt2QCfRDfY0LrUq2q4oK/KypuJYPHgq1A=
2 2 2be3001847cb18a23c403439d9e7d0ace30804e9 0 iD8DBQBExUbjywK+sNU5EO8RAhzxAKCtyHAQUzcTSZTqlfJ0by6vhREwWQCghaQFHfkfN0l9/40EowNhuMOKnJk=
3 3 36a957364b1b89c150f2d0e60a99befe0ee08bd3 0 iD8DBQBFfL2QywK+sNU5EO8RAjYFAKCoGlaWRTeMsjdmxAjUYx6diZxOBwCfY6IpBYsKvPTwB3oktnPt5Rmrlys=
4 4 27230c29bfec36d5540fbe1c976810aefecfd1d2 0 iD8DBQBFheweywK+sNU5EO8RAt7VAKCrqJQWT2/uo2RWf0ZI4bLp6v82jACgjrMdsaTbxRsypcmEsdPhlG6/8F4=
5 5 fb4b6d5fe100b0886f8bc3d6731ec0e5ed5c4694 0 iD8DBQBGgHicywK+sNU5EO8RAgNxAJ0VG8ixAaeudx4sZbhngI1syu49HQCeNUJQfWBgA8bkJ2pvsFpNxwYaX3I=
6 6 23889160905a1b09fffe1c07378e9fc1827606eb 0 iD8DBQBHGTzoywK+sNU5EO8RAr/UAJ0Y8s4jQtzgS+G9vM8z6CWBThZ8fwCcCT5XDj2XwxKkz/0s6UELwjsO3LU=
7 7 bae2e9c838e90a393bae3973a7850280413e091a 0 iD8DBQBH6DO5ywK+sNU5EO8RAsfrAJ0e4r9c9GF/MJsM7Xjd3NesLRC3+ACffj6+6HXdZf8cswAoFPO+DY00oD0=
8 8 d5cbbe2c49cee22a9fbeb9ea41daa0ac4e26b846 0 iD8DBQBINdwsywK+sNU5EO8RAjIUAKCPmlFJSpsPAAUKF+iNHAwVnwmzeQCdEXrL27CWclXuUKdbQC8De7LICtE=
9 9 d2375bbee6d47e62ba8e415c86e83a465dc4dce9 0 iD8DBQBIo1wpywK+sNU5EO8RAmRNAJ94x3OFt6blbqu/yBoypm/AJ44fuACfUaldXcV5z9tht97hSp22DVTEPGc=
10 10 2a67430f92f15ea5159c26b09ec4839a0c549a26 0 iEYEABECAAYFAkk1hykACgkQywK+sNU5EO85QACeNJNUanjc2tl4wUoPHNuv+lSj0ZMAoIm93wSTc/feyYnO2YCaQ1iyd9Nu
11 11 3773e510d433969e277b1863c317b674cbee2065 0 iEYEABECAAYFAklNbbAACgkQywK+sNU5EO8o+gCfeb2/lfIJZMvyDA1m+G1CsBAxfFsAoIa6iAMG8SBY7hW1Q85Yf/LXEvaE
12 12 11a4eb81fb4f4742451591489e2797dc47903277 0 iEYEABECAAYFAklcAnsACgkQywK+sNU5EO+uXwCbBVHNNsLy1g7BlAyQJwadYVyHOXoAoKvtAVO71+bv7EbVoukwTzT+P4Sx
13 13 11efa41037e280d08cfb07c09ad485df30fb0ea8 0 iEYEABECAAYFAkmvJRQACgkQywK+sNU5EO9XZwCeLMgDgPSMWMm6vgjL4lDs2pEc5+0AnRxfiFbpbBfuEFTqKz9nbzeyoBlx
14 14 02981000012e3adf40c4849bd7b3d5618f9ce82d 0 iEYEABECAAYFAknEH3wACgkQywK+sNU5EO+uXwCeI+LbLMmhjU1lKSfU3UWJHjjUC7oAoIZLvYDGOL/tNZFUuatc3RnZ2eje
15 15 196d40e7c885fa6e95f89134809b3ec7bdbca34b 0 iEYEABECAAYFAkpL2X4ACgkQywK+sNU5EO9FOwCfXJycjyKJXsvQqKkHrglwOQhEKS4An36GfKzptfN8b1qNc3+ya/5c2WOM
16 16 3ef6c14a1e8e83a31226f5881b7fe6095bbfa6f6 0 iEYEABECAAYFAkpopLIACgkQywK+sNU5EO8QSgCfZ0ztsd071rOa2lhmp9Fyue/WoI0AoLTei80/xrhRlB8L/rZEf2KBl8dA
17 17 31ec469f9b556f11819937cf68ee53f2be927ebf 0 iEYEABECAAYFAksBuxAACgkQywK+sNU5EO+mBwCfagB+A0txzWZ6dRpug3LEoK7Z1QsAoKpbk8vsLjv6/oRDicSk/qBu33+m
18 18 439d7ea6fe3aa4ab9ec274a68846779153789de9 0 iEYEABECAAYFAksVw0kACgkQywK+sNU5EO/oZwCfdfBEkgp38xq6wN2F4nj+SzofrJIAnjmxt04vaJSeOOeHylHvk6lzuQsw
19 19 296a0b14a68621f6990c54fdba0083f6f20935bf 0 iEYEABECAAYFAks+jCoACgkQywK+sNU5EO9J8wCeMUGF9E/gS2UBsqIz56WS4HMPRPUAoI5J95mwEIK8Clrl7qFRidNI6APq
20 20 4aa619c4c2c09907034d9824ebb1dd0e878206eb 0 iEYEABECAAYFAktm9IsACgkQywK+sNU5EO9XGgCgk4HclRQhexEtooPE5GcUCdB6M8EAn2ptOhMVbIoO+JncA+tNACPFXh0O
21 21 ff2704a8ded37fbebd8b6eb5ec733731d725da8a 0 iEYEABECAAYFAkuRoSQACgkQywK+sNU5EO//3QCeJDc5r2uFyFCtAlpSA27DEE5rrxAAn2FSwTy9fhrB3QAdDQlwkEZcQzDh
22 22 2b01dab594167bc0dd33331dbaa6dca3dca1b3aa 0 iEYEABECAAYFAku1IwIACgkQywK+sNU5EO9MjgCdHLVwkTZlNHxhcznZKBL1rjN+J7cAoLLWi9LTL6f/TgBaPSKOy1ublbaW
23 23 39f725929f0c48c5fb3b90c071fc3066012456ca 0 iEYEABECAAYFAkvclvsACgkQywK+sNU5EO9FSwCeL9i5x8ALW/LE5+lCX6MFEAe4MhwAn1ev5o6SX6GrNdDfKweiemfO2VBk
24 24 fdcf80f26604f233dc4d8f0a5ef9d7470e317e8a 0 iEYEABECAAYFAkvsKTkACgkQywK+sNU5EO9qEACgiSiRGvTG2vXGJ65tUSOIYihTuFAAnRzRIqEVSw8M8/RGeUXRps0IzaCO
25 25 24fe2629c6fd0c74c90bd066e77387c2b02e8437 0 iEYEABECAAYFAkwFLRsACgkQywK+sNU5EO+pJACgp13tPI+pbwKZV+LeMjcQ4H6tCZYAoJebzhd6a8yYx6qiwpJxA9BXZNXy
26 26 f786fc4b8764cd2a5526d259cf2f94d8a66924d9 0 iEYEABECAAYFAkwsyxcACgkQywK+sNU5EO+crACfUpNAF57PmClkSri9nJcBjb2goN4AniPCNaKvnki7TnUsi1u2oxltpKKL
27 27 bf1774d95bde614af3956d92b20e2a0c68c5fec7 0 iEYEABECAAYFAkxVwccACgkQywK+sNU5EO+oFQCeJzwZ+we1fIIyBGCddHceOUAN++cAnjvT6A8ZWW0zV21NXIFF1qQmjxJd
28 28 c00f03a4982e467fb6b6bd45908767db6df4771d 0 iEYEABECAAYFAkxXDqsACgkQywK+sNU5EO/GJACfT9Rz4hZOxPQEs91JwtmfjevO84gAmwSmtfo5mmWSm8gtTUebCcdTv0Kf
29 29 ff5cec76b1c5b6be9c3bb923aae8c3c6d079d6b9 0 iD8DBQBMdo+qywK+sNU5EO8RAqQpAJ975BL2CCAiWMz9SXthNQ9xG181IwCgp4O+KViHPkufZVFn2aTKMNvcr1A=
30 30 93d8bff78c96fe7e33237b257558ee97290048a4 0 iD8DBQBMpfvdywK+sNU5EO8RAsxVAJ0UaL1XB51C76JUBhafc9GBefuMxwCdEWkTOzwvE0SarJBe9i008jhbqW4=
31 31 333421b9e0f96c7bc788e5667c146a58a9440a55 0 iD8DBQBMz0HOywK+sNU5EO8RAlsEAJ0USh6yOG7OrWkADGunVt9QimBQnwCbBqeMnKgSbwEw8jZwE3Iz1mdrYlo=
32 32 4438875ec01bd0fc32be92b0872eb6daeed4d44f 0 iD8DBQBM4WYUywK+sNU5EO8RAhCVAJ0dJswachwFAHALmk1x0RJehxzqPQCbBNskP9n/X689jB+btNTZTyKU/fw=
33 33 6aff4f144ad356311318b0011df0bb21f2c97429 0 iD8DBQBM9uxXywK+sNU5EO8RAv+4AKCDj4qKP16GdPaq1tP6BUwpM/M1OACfRyzLPp/qiiN8xJTWoWYSe/XjJug=
34 34 e3bf16703e2601de99e563cdb3a5d50b64e6d320 0 iD8DBQBNH8WqywK+sNU5EO8RAiQTAJ9sBO+TeiGro4si77VVaQaA6jcRUgCfSA28dBbjj0oFoQwvPoZjANiZBH8=
35 35 a6c855c32ea081da3c3b8ff628f1847ff271482f 0 iD8DBQBNSJJ+ywK+sNU5EO8RAoJaAKCweDEF70fu+r1Zn7pYDXdlk5RuSgCeO9gK/eit8Lin/1n3pO7aYguFLok=
36 36 2b2155623ee2559caf288fd333f30475966c4525 0 iD8DBQBNSJeBywK+sNU5EO8RAm1KAJ4hW9Cm9nHaaGJguchBaPLlAr+O3wCgqgmMok8bdAS06N6PL60PSTM//Gg=
37 37 2616325766e3504c8ae7c84bd15ee610901fe91d 0 iD8DBQBNbWy9ywK+sNU5EO8RAlWCAJ4mW8HbzjJj9GpK98muX7k+7EvEHwCfaTLbC/DH3QEsZBhEP+M8tzL6RU4=
38 38 aa1f3be38ab127280761889d2dca906ca465b5f4 0 iD8DBQBNeQq7ywK+sNU5EO8RAlEOAJ4tlEDdetE9lKfjGgjbkcR8PrC3egCfXCfF3qNVvU/2YYjpgvRwevjvDy0=
39 39 b032bec2c0a651ca0ddecb65714bfe6770f67d70 0 iD8DBQBNlg5kywK+sNU5EO8RAnGEAJ9gmEx6MfaR4XcG2m/93vwtfyzs3gCgltzx8/YdHPwqDwRX/WbpYgi33is=
40 40 3cb1e95676ad089596bd81d0937cad37d6e3b7fb 0 iD8DBQBNvTy4ywK+sNU5EO8RAmp8AJ9QnxK4jTJ7G722MyeBxf0UXEdGwACgtlM7BKtNQfbEH/fOW5y+45W88VI=
41 41 733af5d9f6b22387913e1d11350fb8cb7c1487dd 0 iD8DBQBN5q/8ywK+sNU5EO8RArRGAKCNGT94GKIYtSuwZ57z1sQbcw6uLACfffpbMV4NAPMl8womAwg+7ZPKnIU=
42 42 de9eb6b1da4fc522b1cab16d86ca166204c24f25 0 iD8DBQBODhfhywK+sNU5EO8RAr2+AJ4ugbAj8ae8/K0bYZzx3sascIAg1QCeK3b+zbbVVqd3b7CDpwFnaX8kTd4=
43 43 4a43e23b8c55b4566b8200bf69fe2158485a2634 0 iD8DBQBONzIMywK+sNU5EO8RAj5SAJ0aPS3+JHnyI6bHB2Fl0LImbDmagwCdGbDLp1S7TFobxXudOH49bX45Iik=
44 44 d629f1e89021103f1753addcef6b310e4435b184 0 iD8DBQBOWAsBywK+sNU5EO8RAht4AJwJl9oNFopuGkj5m8aKuf7bqPkoAQCeNrEm7UhFsZKYT5iUOjnMV7s2LaM=
45 45 351a9292e430e35766c552066ed3e87c557b803b 0 iD8DBQBOh3zUywK+sNU5EO8RApFMAKCD3Y/u3avDFndznwqfG5UeTHMlvACfUivPIVQZyDZnhZMq0UhC6zhCEQg=
46 46 384082750f2c51dc917d85a7145748330fa6ef4d 0 iD8DBQBOmd+OywK+sNU5EO8RAgDgAJ9V/X+G7VLwhTpHrZNiOHabzSyzYQCdE2kKfIevJUYB9QLAWCWP6DPwrwI=
47 47 41453d55b481ddfcc1dacb445179649e24ca861d 0 iD8DBQBOsFhpywK+sNU5EO8RAqM6AKCyfxUae3/zLuiLdQz+JR78690eMACfQ6JTBQib4AbE+rUDdkeFYg9K/+4=
48 48 195dbd1cef0c2f9f8bcf4ea303238105f716bda3 0 iD8DBQBO1/fWywK+sNU5EO8RAmoPAKCR5lpv1D6JLURHD8KVLSV4GRVEBgCgnd0Sy78ligNfqAMafmACRDvj7vo=
49 49 6344043924497cd06d781d9014c66802285072e4 0 iD8DBQBPALgmywK+sNU5EO8RAlfhAJ9nYOdWnhfVDHYtDTJAyJtXBAQS9wCgnefoSQt7QABkbGxM+Q85UYEBuD0=
50 50 db33555eafeaf9df1e18950e29439eaa706d399b 0 iD8DBQBPGdzxywK+sNU5EO8RAppkAJ9jOXhUVE/97CPgiMA0pMGiIYnesQCfengAszcBiSiKGugiI8Okc9ghU+Y=
51 51 2aa5b51f310fb3befd26bed99c02267f5c12c734 0 iD8DBQBPKZ9bywK+sNU5EO8RAt1TAJ45r1eJ0YqSkInzrrayg4TVCh0SnQCgm0GA/Ua74jnnDwVQ60lAwROuz1Q=
52 52 53e2cd303ecf8ca7c7eeebd785c34e5ed6b0f4a4 0 iD8DBQBPT/fvywK+sNU5EO8RAnfYAKCn7d0vwqIb100YfWm1F7nFD5B+FACeM02YHpQLSNsztrBCObtqcnfod7Q=
53 53 b9bd95e61b49c221c4cca24e6da7c946fc02f992 0 iD8DBQBPeLsIywK+sNU5EO8RAvpNAKCtKe2gitz8dYn52IRF0hFOPCR7AQCfRJL/RWCFweu2T1vH/mUOCf8SXXc=
54 54 d9e2f09d5488c395ae9ddbb320ceacd24757e055 0 iD8DBQBPju/dywK+sNU5EO8RArBYAJ9xtifdbk+hCOJO8OZa4JfHX8OYZQCeKPMBaBWiT8N/WHoOm1XU0q+iono=
55 55 00182b3d087909e3c3ae44761efecdde8f319ef3 0 iD8DBQBPoFhIywK+sNU5EO8RAhzhAKCBj1n2jxPTkZNJJ5pSp3soa+XHIgCgsZZpAQxOpXwCp0eCdNGe0+pmxmg=
56 56 5983de86462c5a9f42a3ad0f5e90ce5b1d221d25 0 iD8DBQBPovNWywK+sNU5EO8RAhgiAJ980T91FdPTRMmVONDhpkMsZwVIMACgg3bKvoWSeuCW28llUhAJtUjrMv0=
57 57 85a358df5bbbe404ca25730c9c459b34263441dc 0 iD8DBQBPyZsWywK+sNU5EO8RAnpLAJ48qrGDJRT+pteS0mSQ11haqHstPwCdG4ccGbk+0JHb7aNy8/NRGAOqn9w=
58 58 b013baa3898e117959984fc64c29d8c784d2f28b 0 iD8DBQBP8QOPywK+sNU5EO8RAqimAKCFRSx0lvG6y8vne2IhNG062Hn0dACeMLI5/zhpWpHBIVeAAquYfx2XFeA=
59 59 7f5094bb3f423fc799e471aac2aee81a7ce57a0b 0 iD8DBQBQGiL8ywK+sNU5EO8RAq5oAJ4rMMCPx6O+OuzNXVOexogedWz/QgCeIiIxLd76I4pXO48tdXhr0hQcBuM=
60 60 072209ae4ddb654eb2d5fd35bff358c738414432 0 iD8DBQBQQkq0ywK+sNU5EO8RArDTAJ9nk5CySnNAjAXYvqvx4uWCw9ThZwCgqmFRehH/l+oTwj3f8nw8u8qTCdc=
61 61 b3f0f9a39c4e1d0250048cd803ab03542d6f140a 0 iD8DBQBQamltywK+sNU5EO8RAlsqAJ4qF/m6aFu4mJCOKTiAP5RvZFK02ACfawYShUZO6OXEFfveU0aAxDR0M1k=
62 62 d118a4f4fd16d9b558ec3f3e87bfee772861d2b7 0 iD8DBQBQgPV5ywK+sNU5EO8RArylAJ0abcx5NlDjyv3ZDWpAfRIHyRsJtQCgn4TMuEayqgxzrvadQZHdTEU2g38=
63 63 195ad823b5d58c68903a6153a25e3fb4ed25239d 0 iD8DBQBQkuT9ywK+sNU5EO8RAhB4AKCeerItoK2Jipm2cVf4euGofAa/WACeJj3TVd4pFILpb+ogj7ebweFLJi0=
64 64 0c10cf8191469e7c3c8844922e17e71a176cb7cb 0 iD8DBQBQvQWoywK+sNU5EO8RAnq3AJoCn98u4geFx5YaQaeh99gFhCd7bQCgjoBwBSUyOvGd0yBy60E3Vv3VZhM=
65 65 a4765077b65e6ae29ba42bab7834717b5072d5ba 0 iD8DBQBQ486sywK+sNU5EO8RAhmJAJ90aLfLKZhmcZN7kqphigQJxiFOQACeJ5IUZxjGKH4xzi3MrgIcx9n+dB0=
66 66 f5fbe15ca7449f2c9a3cf817c86d0ae68b307214 0 iD8DBQBQ+yuYywK+sNU5EO8RAm9JAJoD/UciWvpGeKBcpGtZJBFJVcL/HACghDXSgQ+xQDjB+6uGrdgAQsRR1Lg=
67 67 a6088c05e43a8aee0472ca3a4f6f8d7dd914ebbf 0 iD8DBQBRDDROywK+sNU5EO8RAh75AJ9uJCGoCWnP0Lv/+XuYs4hvUl+sAgCcD36QgAnuw8IQXrvv684BAXAnHcA=
68 68 7511d4df752e61fe7ae4f3682e0a0008573b0402 0 iD8DBQBRFYaoywK+sNU5EO8RAuErAJoDyhXn+lptU3+AevVdwAIeNFyR2gCdHzPHyWd+JDeWCUR+pSOBi8O2ppM=
69 69 5b7175377babacce80a6c1e12366d8032a6d4340 0 iD8DBQBRMCYgywK+sNU5EO8RAq1/AKCWKlt9ysibyQgYwoxxIOZv5J8rpwCcDSHQaaf1fFZUTnQsOePwcM2Y/Sg=
70 70 50c922c1b5145dab8baefefb0437d363b6a6c21c 0 iD8DBQBRWnUnywK+sNU5EO8RAuQRAJwM42cJqJPeqJ0jVNdMqKMDqr4dSACeP0cRVGz1gitMuV0x8f3mrZrqc7I=
71 71 8a7bd2dccd44ed571afe7424cd7f95594f27c092 0 iD8DBQBRXfBvywK+sNU5EO8RAn+LAKCsMmflbuXjYRxlzFwId5ptm8TZcwCdGkyLbZcASBOkzQUm/WW1qfknJHU=
72 72 292cd385856d98bacb2c3086f8897bc660c2beea 0 iD8DBQBRcM0BywK+sNU5EO8RAjp4AKCJBykQbvXhKuvLSMxKx3a2TBiXcACfbr/kLg5GlZTF/XDPmY+PyHgI/GM=
73 73 23f785b38af38d2fca6b8f3db56b8007a84cd73a 0 iD8DBQBRgZwNywK+sNU5EO8RAmO4AJ4u2ILGuimRP6MJgE2t65LZ5dAdkACgiENEstIdrlFC80p+sWKD81kKIYI=
74 74 ddc7a6be20212d18f3e27d9d7e6f079a66d96f21 0 iD8DBQBRkswvywK+sNU5EO8RAiYYAJsHTHyHbJeAgmGvBTmDrfcKu4doUgCeLm7eGBjx7yAPUvEtxef8rAkQmXI=
75 75 cceaf7af4c9e9e6fa2dbfdcfe9856c5da69c4ffd 0 iD8DBQBRqnFLywK+sNU5EO8RAsWNAJ9RR6t+y1DLFc2HeH0eN9VfZAKF9gCeJ8ezvhtKq/LMs0/nvcgKQc/d5jk=
76 76 009794acc6e37a650f0fae37872e733382ac1c0c 0 iD8DBQBR0guxywK+sNU5EO8RArNkAKCq9pMihVzP8Os5kCmgbWpe5C37wgCgqzuPZTHvAsXF5wTyaSTMVa9Ccq4=
77 77 f0d7721d7322dcfb5af33599c2543f27335334bb 0 iD8DBQBR8taaywK+sNU5EO8RAqeEAJ4idDhhDuEsgsUjeQgWNj498matHACfT67gSF5w0ylsrBx1Hb52HkGXDm0=
78 78 f37b5a17e6a0ee17afde2cdde5393dd74715fb58 0 iD8DBQBR+ymFywK+sNU5EO8RAuSdAJkBMcd9DAZ3rWE9WGKPm2YZ8LBoXACfXn/wbEsVy7ZgJoUwiWmHSnQaWCI=
79 79 335a558f81dc73afeab4d7be63617392b130117f 0 iQIVAwUAUiZrIyBXgaxoKi1yAQK2iw//cquNqqSkc8Re5/TZT9I6NH+lh6DbOKjJP0Xl1Wqq0K+KSIUgZG4G32ovaEb2l5X0uY+3unRPiZ0ebl0YSw4Fb2ZiPIADXLBTOYRrY2Wwd3tpJeGI6wEgZt3SfcITV/g7NJrCjT3FlYoSOIayrExM80InSdcEM0Q3Rx6HKzY2acyxzgZeAtAW5ohFvHilSvY6p5Gcm4+QptMxvw45GPdreUmjeXZxNXNXZ8P+MjMz/QJbai/N7PjmK8lqnhkBsT48Ng/KhhmOkGntNJ2/ImBWLFGcWngSvJ7sfWwnyhndvGhe0Hq1NcCf7I8TjNDxU5TR+m+uW7xjXdLoDbUjBdX4sKXnh8ZjbYiODKBOrrDq25cf8nA/tnpKyE/qsVy60kOk6loY4XKiYmn1V49Ta0emmDx0hqo3HgxHHsHX0NDnGdWGol7cPRET0RzVobKq1A0jnrhPooWidvLh9bPzLonrWDo+ib+DuySoRkuYUK4pgZJ2mbg6daFOBEZygkSyRB8bo1UQUP7EgQDrWe4khb/5GHEfDkrQz3qu/sXvc0Ir1mOUWBFPHC2DjjCn/oMJuUkG1SwM8l2Bfv7h67ssES6YQ2+RjOix4yid7EXS/Ogl45PzCIPSI5+BbNs10JhE0w5uErBHlF53EDTe/TSLc+GU6DB6PP6dH912Njdr3jpNSUQ=
80 80 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
@@ -1,267 +1,268 b''
1 1 d40cc5aacc31ed673d9b5b24f98bee78c283062c 0.4f
2 2 1c590d34bf61e2ea12c71738e5a746cd74586157 0.4e
3 3 7eca4cfa8aad5fce9a04f7d8acadcd0452e2f34e 0.4d
4 4 b4d0c3786ad3e47beacf8412157326a32b6d25a4 0.4c
5 5 f40273b0ad7b3a6d3012fd37736d0611f41ecf54 0.5
6 6 0a28dfe59f8fab54a5118c5be4f40da34a53cdb7 0.5b
7 7 12e0fdbc57a0be78f0e817fd1d170a3615cd35da 0.6
8 8 4ccf3de52989b14c3d84e1097f59e39a992e00bd 0.6b
9 9 eac9c8efcd9bd8244e72fb6821f769f450457a32 0.6c
10 10 979c049974485125e1f9357f6bbe9c1b548a64c3 0.7
11 11 3a56574f329a368d645853e0f9e09472aee62349 0.8
12 12 6a03cff2b0f5d30281e6addefe96b993582f2eac 0.8.1
13 13 35fb62a3a673d5322f6274a44ba6456e5e4b3b37 0.9
14 14 2be3001847cb18a23c403439d9e7d0ace30804e9 0.9.1
15 15 36a957364b1b89c150f2d0e60a99befe0ee08bd3 0.9.2
16 16 27230c29bfec36d5540fbe1c976810aefecfd1d2 0.9.3
17 17 fb4b6d5fe100b0886f8bc3d6731ec0e5ed5c4694 0.9.4
18 18 23889160905a1b09fffe1c07378e9fc1827606eb 0.9.5
19 19 bae2e9c838e90a393bae3973a7850280413e091a 1.0
20 20 d5cbbe2c49cee22a9fbeb9ea41daa0ac4e26b846 1.0.1
21 21 d2375bbee6d47e62ba8e415c86e83a465dc4dce9 1.0.2
22 22 2a67430f92f15ea5159c26b09ec4839a0c549a26 1.1
23 23 3773e510d433969e277b1863c317b674cbee2065 1.1.1
24 24 11a4eb81fb4f4742451591489e2797dc47903277 1.1.2
25 25 11efa41037e280d08cfb07c09ad485df30fb0ea8 1.2
26 26 02981000012e3adf40c4849bd7b3d5618f9ce82d 1.2.1
27 27 196d40e7c885fa6e95f89134809b3ec7bdbca34b 1.3
28 28 3ef6c14a1e8e83a31226f5881b7fe6095bbfa6f6 1.3.1
29 29 31ec469f9b556f11819937cf68ee53f2be927ebf 1.4
30 30 439d7ea6fe3aa4ab9ec274a68846779153789de9 1.4.1
31 31 296a0b14a68621f6990c54fdba0083f6f20935bf 1.4.2
32 32 4aa619c4c2c09907034d9824ebb1dd0e878206eb 1.4.3
33 33 ff2704a8ded37fbebd8b6eb5ec733731d725da8a 1.5
34 34 2b01dab594167bc0dd33331dbaa6dca3dca1b3aa 1.5.1
35 35 39f725929f0c48c5fb3b90c071fc3066012456ca 1.5.2
36 36 fdcf80f26604f233dc4d8f0a5ef9d7470e317e8a 1.5.3
37 37 24fe2629c6fd0c74c90bd066e77387c2b02e8437 1.5.4
38 38 f786fc4b8764cd2a5526d259cf2f94d8a66924d9 1.6
39 39 bf1774d95bde614af3956d92b20e2a0c68c5fec7 1.6.1
40 40 c00f03a4982e467fb6b6bd45908767db6df4771d 1.6.2
41 41 ff5cec76b1c5b6be9c3bb923aae8c3c6d079d6b9 1.6.3
42 42 93d8bff78c96fe7e33237b257558ee97290048a4 1.6.4
43 43 333421b9e0f96c7bc788e5667c146a58a9440a55 1.7
44 44 4438875ec01bd0fc32be92b0872eb6daeed4d44f 1.7.1
45 45 6aff4f144ad356311318b0011df0bb21f2c97429 1.7.2
46 46 e3bf16703e2601de99e563cdb3a5d50b64e6d320 1.7.3
47 47 a6c855c32ea081da3c3b8ff628f1847ff271482f 1.7.4
48 48 2b2155623ee2559caf288fd333f30475966c4525 1.7.5
49 49 2616325766e3504c8ae7c84bd15ee610901fe91d 1.8
50 50 aa1f3be38ab127280761889d2dca906ca465b5f4 1.8.1
51 51 b032bec2c0a651ca0ddecb65714bfe6770f67d70 1.8.2
52 52 3cb1e95676ad089596bd81d0937cad37d6e3b7fb 1.8.3
53 53 733af5d9f6b22387913e1d11350fb8cb7c1487dd 1.8.4
54 54 de9eb6b1da4fc522b1cab16d86ca166204c24f25 1.9
55 55 4a43e23b8c55b4566b8200bf69fe2158485a2634 1.9.1
56 56 d629f1e89021103f1753addcef6b310e4435b184 1.9.2
57 57 351a9292e430e35766c552066ed3e87c557b803b 1.9.3
58 58 384082750f2c51dc917d85a7145748330fa6ef4d 2.0-rc
59 59 41453d55b481ddfcc1dacb445179649e24ca861d 2.0
60 60 195dbd1cef0c2f9f8bcf4ea303238105f716bda3 2.0.1
61 61 6344043924497cd06d781d9014c66802285072e4 2.0.2
62 62 db33555eafeaf9df1e18950e29439eaa706d399b 2.1-rc
63 63 2aa5b51f310fb3befd26bed99c02267f5c12c734 2.1
64 64 53e2cd303ecf8ca7c7eeebd785c34e5ed6b0f4a4 2.1.1
65 65 b9bd95e61b49c221c4cca24e6da7c946fc02f992 2.1.2
66 66 d9e2f09d5488c395ae9ddbb320ceacd24757e055 2.2-rc
67 67 00182b3d087909e3c3ae44761efecdde8f319ef3 2.2
68 68 5983de86462c5a9f42a3ad0f5e90ce5b1d221d25 2.2.1
69 69 85a358df5bbbe404ca25730c9c459b34263441dc 2.2.2
70 70 b013baa3898e117959984fc64c29d8c784d2f28b 2.2.3
71 71 a06e2681dd1786e2354d84a5fa9c1c88dd4fa3e0 2.3-rc
72 72 7f5094bb3f423fc799e471aac2aee81a7ce57a0b 2.3
73 73 072209ae4ddb654eb2d5fd35bff358c738414432 2.3.1
74 74 b3f0f9a39c4e1d0250048cd803ab03542d6f140a 2.3.2
75 75 d118a4f4fd16d9b558ec3f3e87bfee772861d2b7 2.4-rc
76 76 195ad823b5d58c68903a6153a25e3fb4ed25239d 2.4
77 77 0c10cf8191469e7c3c8844922e17e71a176cb7cb 2.4.1
78 78 a4765077b65e6ae29ba42bab7834717b5072d5ba 2.4.2
79 79 f5fbe15ca7449f2c9a3cf817c86d0ae68b307214 2.5-rc
80 80 a6088c05e43a8aee0472ca3a4f6f8d7dd914ebbf 2.5
81 81 7511d4df752e61fe7ae4f3682e0a0008573b0402 2.5.1
82 82 5b7175377babacce80a6c1e12366d8032a6d4340 2.5.2
83 83 50c922c1b5145dab8baefefb0437d363b6a6c21c 2.5.3
84 84 8a7bd2dccd44ed571afe7424cd7f95594f27c092 2.5.4
85 85 292cd385856d98bacb2c3086f8897bc660c2beea 2.6-rc
86 86 23f785b38af38d2fca6b8f3db56b8007a84cd73a 2.6
87 87 ddc7a6be20212d18f3e27d9d7e6f079a66d96f21 2.6.1
88 88 cceaf7af4c9e9e6fa2dbfdcfe9856c5da69c4ffd 2.6.2
89 89 009794acc6e37a650f0fae37872e733382ac1c0c 2.6.3
90 90 f0d7721d7322dcfb5af33599c2543f27335334bb 2.7-rc
91 91 f37b5a17e6a0ee17afde2cdde5393dd74715fb58 2.7
92 92 335a558f81dc73afeab4d7be63617392b130117f 2.7.1
93 93 e7fa36d2ad3a7944a52dca126458d6f482db3524 2.7.2
94 94 1596f2d8f2421314b1ddead8f7d0c91009358994 2.8-rc
95 95 d825e4025e39d1c39db943cdc89818abd0a87c27 2.8
96 96 209e04a06467e2969c0cc6501335be0406d46ef0 2.8.1
97 97 ca387377df7a3a67dbb90b6336b781cdadc3ef41 2.8.2
98 98 8862469e16f9236208581b20de5f96bd13cc039d 2.9-rc
99 99 3cec5134e9c4bceab6a00c60f52a4f80677a78f2 2.9
100 100 b96cb15ec9e04d8ac5ee08b34fcbbe4200588965 2.9.1
101 101 3f83fc5cfe715d292069ee8417c83804f6c6c1e4 2.9.2
102 102 564f55b251224f16508dd1311452db7780dafe2b 3.0-rc
103 103 2195ac506c6ababe86985b932f4948837c0891b5 3.0
104 104 269c80ee5b3cb3684fa8edc61501b3506d02eb10 3.0.1
105 105 2d8cd3d0e83c7336c0cb45a9f88638363f993848 3.0.2
106 106 6c36dc6cd61a0e1b563f1d51e55bdf4dacf12162 3.1-rc
107 107 3178e49892020336491cdc6945885c4de26ffa8b 3.1
108 108 5dc91146f35369949ea56b40172308158b59063a 3.1.1
109 109 f768c888aaa68d12dd7f509dcc7f01c9584357d0 3.1.2
110 110 7f8d16af8cae246fa5a48e723d48d58b015aed94 3.2-rc
111 111 ced632394371a36953ce4d394f86278ae51a2aae 3.2
112 112 643c58303fb0ec020907af28b9e486be299ba043 3.2.1
113 113 902554884335e5ca3661d63be9978eb4aec3f68a 3.2.2
114 114 6dad422ecc5adb63d9fa649eeb8e05a5f9bc4900 3.2.3
115 115 1265a3a71d75396f5d4cf6935ae7d9ba5407a547 3.2.4
116 116 db8e3f7948b1fdeb9ad12d448fc3525759908b9f 3.3-rc
117 117 fbdd5195528fae4f41feebc1838215c110b25d6a 3.3
118 118 5b4ed033390bf6e2879c8f5c28c84e1ee3b87231 3.3.1
119 119 07a92bbd02e5e3a625e0820389b47786b02b2cea 3.3.2
120 120 2e2e9a0750f91a6fe0ad88e4de34f8efefdcab08 3.3.3
121 121 e89f909edffad558b56f4affa8239e4832f88de0 3.4-rc
122 122 8cc6036bca532e06681c5a8fa37efaa812de67b5 3.4
123 123 ed18f4acf435a2824c6f49fba40f42b9df5da7ad 3.4.1
124 124 540cd0ddac49c1125b2e013aa2ff18ecbd4dd954 3.4.2
125 125 96a38d44ba093bd1d1ecfd34119e94056030278b 3.5-rc
126 126 21aa1c313b05b1a85f8ffa1120d51579ddf6bf24 3.5
127 127 1a45e49a6bed023deb229102a8903234d18054d3 3.5.1
128 128 9a466b9f9792e3ad7ae3fc6c43c3ff2e136b718d 3.5.2
129 129 b66e3ca0b90c3095ea28dfd39aa24247bebf5c20 3.6-rc
130 130 47dd34f2e7272be9e3b2a5a83cd0d20be44293f4 3.6
131 131 1aa5083cbebbe7575c88f3402ab377539b484897 3.6.1
132 132 2d437a0f3355834a9485bbbeb30a52a052c98f19 3.6.2
133 133 ea389970c08449440587712117f178d33bab3f1e 3.6.3
134 134 158bdc8965720ca4061f8f8d806563cfc7cdb62e 3.7-rc
135 135 2408645de650d8a29a6ce9e7dce601d8dd0d1474 3.7
136 136 b698abf971e7377d9b7ec7fc8c52df45255b0329 3.7.1
137 137 d493d64757eb45ada99fcb3693e479a51b7782da 3.7.2
138 138 ae279d4a19e9683214cbd1fe8298cf0b50571432 3.7.3
139 139 740156eedf2c450aee58b1a90b0e826f47c5da64 3.8-rc
140 140 f85de28eae32e7d3064b1a1321309071bbaaa069 3.8
141 141 a56296f55a5e1038ea5016dace2076b693c28a56 3.8.1
142 142 aaabed77791a75968a12b8c43ad263631a23ee81 3.8.2
143 143 a9764ab80e11bcf6a37255db7dd079011f767c6c 3.8.3
144 144 26a5d605b8683a292bb89aea11f37a81b06ac016 3.8.4
145 145 519bb4f9d3a47a6e83c2b414d58811ed38f503c2 3.9-rc
146 146 299546f84e68dbb9bd026f0f3a974ce4bdb93686 3.9
147 147 ccd436f7db6d5d7b9af89715179b911d031d44f1 3.9.1
148 148 149433e68974eb5c63ccb03f794d8b57339a80c4 3.9.2
149 149 438173c415874f6ac653efc1099dec9c9150e90f 4.0-rc
150 150 eab27446995210c334c3d06f1a659e3b9b5da769 4.0
151 151 b3b1ae98f6a0e14c1e1ba806a6c18e193b6dae5c 4.0.1
152 152 e69874dc1f4e142746ff3df91e678a09c6fc208c 4.0.2
153 153 a1dd2c0c479e0550040542e392e87bc91262517e 4.1-rc
154 154 e1526da1e6d84e03146151c9b6e6950fe9a83d7d 4.1
155 155 25703b624d27e3917d978af56d6ad59331e0464a 4.1.1
156 156 ed5b25874d998ababb181a939dd37a16ea644435 4.1.2
157 157 77eaf9539499a1b8be259ffe7ada787d07857f80 4.1.3
158 158 616e788321cc4ae9975b7f0c54c849f36d82182b 4.2-rc
159 159 bb96d4a497432722623ae60d9bc734a1e360179e 4.2
160 160 c850f0ed54c1d42f9aa079ad528f8127e5775217 4.2.1
161 161 26c49ed51a698ec016d2b4c6b44ca3c3f73cc788 4.2.2
162 162 857876ebaed4e315f63157bd157d6ce553c7ab73 4.3-rc
163 163 5544af8622863796a0027566f6b646e10d522c4c 4.3
164 164 943c91326b23954e6e1c6960d0239511f9530258 4.2.3
165 165 3fee7f7d2da04226914c2258cc2884dc27384fd7 4.3.1
166 166 920977f72c7b70acfdaf56ab35360584d7845827 4.3.2
167 167 2f427b57bf9019c6dc3750baa539dc22c1be50f6 4.3.3
168 168 1e2454b60e5936f5e77498cab2648db469504487 4.4-rc
169 169 0ccb43d4cf01d013ae05917ec4f305509f851b2d 4.4
170 170 cabc840ffdee8a72f3689fb77dd74d04fdc2bc04 4.4.1
171 171 a92b9f8e11ba330614cdfd6af0e03b15c1ff3797 4.4.2
172 172 27b6df1b5adbdf647cf5c6675b40575e1b197c60 4.5-rc
173 173 d334afc585e29577f271c5eda03378736a16ca6b 4.5
174 174 369aadf7a3264b03c8b09efce715bc41e6ab4a9b 4.5.1
175 175 8bba684efde7f45add05f737952093bb2aa07155 4.5.2
176 176 7de7bd407251af2bc98e5b809c8598ee95830daf 4.5.3
177 177 ed5448edcbfa747b9154099e18630e49024fd47b 4.6rc0
178 178 1ec874717d8a93b19e0d50628443e0ee5efab3a9 4.6rc1
179 179 6614cac550aea66d19c601e45efd1b7bd08d7c40 4.6
180 180 9c5ced5276d6e7d54f7c3dadf5247b7ee98ec79c 4.6.1
181 181 0b63a6743010dfdbf8a8154186e119949bdaa1cc 4.6.2
182 182 e90130af47ce8dd53a3109aed9d15876b3e7dee8 4.7rc0
183 183 33ac6a72308a215e6086fbced347ec10aa963b0a 4.7
184 184 ede3bf31fe63677fdf5bd8db687977d4e3d792ed 4.7.1
185 185 5405cb1a79010ac50c58cd84e6f50c4556bf2a4c 4.7.2
186 186 956ec6f1320df26f3133ec40f3de866ea0695fd7 4.8rc0
187 187 a91a2837150bdcb27ae76b3646e6c93cd6a15904 4.8
188 188 1c8c54cf97256f4468da2eb4dbee24f7f3888e71 4.8.1
189 189 197f092b2cd9691e2a55d198f717b231af9be6f9 4.8.2
190 190 593718ff5844cad7a27ee3eb5adad89ac8550949 4.9rc0
191 191 83377b4b4ae0e9a6b8e579f7b0a693b8cf5c3b10 4.9
192 192 4ea21df312ec7159c5b3633096b6ecf68750b0dd 4.9.1
193 193 4a8d9ed864754837a185a642170cde24392f9abf 5.0rc0
194 194 07e479ef7c9639be0029f00e6a722b96dcc05fee 5.0
195 195 c3484ddbdb9621256d597ed86b90d229c59c2af9 5.0.1
196 196 97ada9b8d51bef24c5cb4cdca4243f0db694ab6e 5.0.2
197 197 e386b5f4f8360dbb43a576dd9b1368e386fefa5b 5.1rc0
198 198 e91930d712e8507d1bc1b2dffd96c83edc4cbed3 5.1
199 199 a4e32fd539ab41489a51b2aa88bda9a73b839562 5.1.1
200 200 181e52f2b62f4768aa0d988936c929dc7c4a41a0 5.1.2
201 201 59338f9561099de77c684c00f76507f11e46ebe8 5.2rc0
202 202 ca3dca416f8d5863ca6f5a4a6a6bb835dcd5feeb 5.2
203 203 a50fecefa691c9b72a99e49aa6fe9dd13943c2bf 5.2.1
204 204 b4c82b70418022e67cc0e69b1aa3c3aa43aa1d29 5.2.2
205 205 84a0102c05c7852c8215ef6cf21d809927586b69 5.3rc0
206 206 e4344e463c0c888a2f437b78b5982ecdf3f6650a 5.3rc1
207 207 7f5410dfc8a64bb587d19637deb95d378fd1eb5c 5.3
208 208 6d121acbb82e65fe4dd3c2318a1b61981b958492 5.3.1
209 209 8fca7e8449a847e3cf1054f2c07b51237699fad3 5.3.2
210 210 26ce8e7515036d3431a03aaeb7bc72dd96cb1112 5.4rc0
211 211 cf3e07d7648a4371ce584d15dd692e7a6845792f 5.4
212 212 065704cbdbdbb05dcd6bb814eb9bbdd982211b28 5.4.1
213 213 0ea9c86fac8974cd74dc12ea681c8986eb6da6c4 5.4.2
214 214 28163c5de797e5416f9b588940f4608269b4d50a 5.5rc0
215 215 7fc3c5fbc65f6fe85d70ea63923b8767dda4f2e0 5.5
216 216 f62bb5d07848ca598aa860a517394130b61bf2ee 5.5.1
217 217 07731064ac41dacdf0ec869ebd05c2e848c14fbf 5.5.2
218 218 0e06a7ab9e0d5c65af4e511aee1e0342998799df 5.6rc0
219 219 18c17d63fdabd009e70bf994e5efb7db422f4f7f 5.6
220 220 1d5189a57405ceca5aa244052c9f948977f4699b 5.6.1
221 221 9da65e3cf3706ff41e08b311381c588440c27baf 5.7rc0
222 222 0e2e7300f4302b02412b0b734717697049494c4c 5.7
223 223 d5d9177c0045d206db575bae6daa98e2cb2fe5bc 5.7.1
224 224 f67b8946bb1b6cfa8328dbf8d6a9128b69ccdcb4 5.8rc0
225 225 8d2b62d716b095507effaa8d56f87cd27ba659ab 5.8rc1
226 226 067f2c53fb24506c9e9fb4639871b13b19a85f8a 5.8
227 227 411dc27fd9fd076d6a031a08fcaace659afe2fe3 5.8.1
228 228 d7515d29761d5ada7d9c765f517db67db75dea9a 5.9rc0
229 229 2813d406b03607cdb8c06cb04c44efcc9a79d9a2 5.9rc1
230 230 53221078e0de65d1a821ce5311dec45a7a978301 5.9
231 231 86a60679cf619e14cee9442f865fcf31b142cb9f 5.9.1
232 232 750920b18aaaddd654756be40dec59d90f2643be 5.9.2
233 233 6ee0244fc1cf889ae543d2ce0ec45201ae0be6e1 5.9.3
234 234 a44bb185f6bdbecc754996d8386722e2f0123b0a 6.0rc0
235 235 5d08b289e2e526259d7d5ea32b70fe76d5b327d7 6.0
236 236 799fdf4cca80cb9ae40537a90995e6bd163ebc0b 6.0.1
237 237 75676122c2bf7594ac732b7388db4c74c648b365 6.0.2
238 238 dcec16e799ddb6d33fcd11b04af530250a417a58 6.0.3
239 239 c00d3ce4e94bb0ee8d809e25e1dcb2a5fab84e2c 6.1rc0
240 240 d4486810a1795fba9521449b8885ced034f3a6dd 6.1
241 241 5bd6bcd31dd1ebb63b8914b00064f96297267af7 6.1.1
242 242 0ddd5e1f5f67438af85d12e4ce6c39021dde9916 6.1.2
243 243 6b10151b962108f65bfa12b3918b1021ca334f73 6.1.3
244 244 0cc5f74ff7f0f4ac2427096bddbe102dbc2453ae 6.1.4
245 245 288de6f5d724bba7bf1669e2838f196962bb7528 6.2rc0
246 246 094a5fa3cf52f936e0de3f1e507c818bee5ece6b 6.2
247 247 f69bffd00abe3a1b94d1032eb2c92e611d16a192 6.2.1
248 248 b5c8524827d20fe2e0ca8fb1234a0fe35a1a36c7 6.2.2
249 249 dbdee8ac3e3fcdda1fa55b90c0a235125b7f8e6f 6.2.3
250 250 a3356ab610fc50000cf0ba55c424a4d96da11db7 6.3rc0
251 251 04f1dba53c961dfdb875c8469adc96fa999cfbed 6.3.0
252 252 04f1dba53c961dfdb875c8469adc96fa999cfbed 6.3
253 253 04f1dba53c961dfdb875c8469adc96fa999cfbed 6.3.0
254 254 0000000000000000000000000000000000000000 6.3.0
255 255 c890d8b8bc59b18e5febf60caada629df5356ee2 6.3.1
256 256 59466b13a3ae0e29a5d4f485393e516cfbb057d0 6.3.2
257 257 8830004967ad865ead89c28a410405a6e71e0796 6.3.3
258 258 05de4896508e8ec387b33eb30d8aab78d1c8e9e4 6.4rc0
259 259 f14864fffdcab725d9eac6d4f4c07be05a35f59a 6.4
260 260 83ea6ce48b4fd09fb79c4e34cc5750c805699a53 6.4.1
261 261 f952be90b0514a576dcc8bbe758ce3847faba9bb 6.4.2
262 262 fc445f8abcf90b33db7c463816a1b3560681767f 6.4.3
263 263 da372c745e0f053bb7a64e74cccd15810d96341d 6.4.4
264 264 271a4ab29605ffa0bae5d3208eaa21a95427ff92 6.4.5
265 265 bb42988c7e156931b0ff1e93732b98173ebbcb7f 6.5rc0
266 266 3ffc7209bbae5804a53084c9dc2d41139e88c867 6.5
267 267 787af4e0e8b787e1b77a8059926b123730a4cd01 6.5.1
268 5a8b5420103937fca97c584c5162178eed828ada 6.5.2
@@ -1,965 +1,966 b''
1 1 # transaction.py - simple journaling scheme for mercurial
2 2 #
3 3 # This transaction scheme is intended to gracefully handle program
4 4 # errors and interruptions. More serious failures like system crashes
5 5 # can be recovered with an fsck-like tool. As the whole repository is
6 6 # effectively log-structured, this should amount to simply truncating
7 7 # anything that isn't referenced in the changelog.
8 8 #
9 9 # Copyright 2005, 2006 Olivia Mackall <olivia@selenic.com>
10 10 #
11 11 # This software may be used and distributed according to the terms of the
12 12 # GNU General Public License version 2 or any later version.
13 13
14 14 import errno
15 15 import os
16 16
17 17 from .i18n import _
18 18 from . import (
19 encoding,
19 20 error,
20 21 pycompat,
21 22 util,
22 23 )
23 24 from .utils import stringutil
24 25
25 26 version = 2
26 27
27 28 GEN_GROUP_ALL = b'all'
28 29 GEN_GROUP_PRE_FINALIZE = b'prefinalize'
29 30 GEN_GROUP_POST_FINALIZE = b'postfinalize'
30 31
31 32
32 33 def active(func):
33 34 def _active(self, *args, **kwds):
34 35 if self._count == 0:
35 36 raise error.ProgrammingError(
36 37 b'cannot use transaction when it is already committed/aborted'
37 38 )
38 39 return func(self, *args, **kwds)
39 40
40 41 return _active
41 42
42 43
43 44 UNDO_BACKUP = b'%s.backupfiles'
44 45
45 46 UNDO_FILES_MAY_NEED_CLEANUP = [
46 47 # legacy entries that might exists on disk from previous version:
47 48 (b'store', b'%s.narrowspec'),
48 49 (b'plain', b'%s.narrowspec.dirstate'),
49 50 (b'plain', b'%s.branch'),
50 51 (b'plain', b'%s.bookmarks'),
51 52 (b'store', b'%s.phaseroots'),
52 53 (b'plain', b'%s.dirstate'),
53 54 # files actually in uses today:
54 55 (b'plain', b'%s.desc'),
55 56 # Always delete undo last to make sure we detect that a clean up is needed if
56 57 # the process is interrupted.
57 58 (b'store', b'%s'),
58 59 ]
59 60
60 61
61 62 def cleanup_undo_files(report, vfsmap, undo_prefix=b'undo'):
62 63 """remove "undo" files used by the rollback logic
63 64
64 65 This is useful to prevent rollback running in situation were it does not
65 66 make sense. For example after a strip.
66 67 """
67 68 backup_listing = UNDO_BACKUP % undo_prefix
68 69
69 70 backup_entries = []
70 71 undo_files = []
71 72 svfs = vfsmap[b'store']
72 73 try:
73 74 with svfs(backup_listing) as f:
74 75 backup_entries = read_backup_files(report, f)
75 76 except OSError as e:
76 77 if e.errno != errno.ENOENT:
77 78 msg = _(b'could not read %s: %s\n')
78 79 msg %= (svfs.join(backup_listing), stringutil.forcebytestr(e))
79 80 report(msg)
80 81
81 82 for location, f, backup_path, c in backup_entries:
82 83 if location in vfsmap and backup_path:
83 84 undo_files.append((vfsmap[location], backup_path))
84 85
85 86 undo_files.append((svfs, backup_listing))
86 87 for location, undo_path in UNDO_FILES_MAY_NEED_CLEANUP:
87 88 undo_files.append((vfsmap[location], undo_path % undo_prefix))
88 89 for undovfs, undofile in undo_files:
89 90 try:
90 91 undovfs.unlink(undofile)
91 92 except OSError as e:
92 93 if e.errno != errno.ENOENT:
93 94 msg = _(b'error removing %s: %s\n')
94 95 msg %= (undovfs.join(undofile), stringutil.forcebytestr(e))
95 96 report(msg)
96 97
97 98
98 99 def _playback(
99 100 journal,
100 101 report,
101 102 opener,
102 103 vfsmap,
103 104 entries,
104 105 backupentries,
105 106 unlink=True,
106 107 checkambigfiles=None,
107 108 ):
108 109 """rollback a transaction :
109 110 - truncate files that have been appended to
110 111 - restore file backups
111 112 - delete temporary files
112 113 """
113 114 backupfiles = []
114 115
115 116 def restore_one_backup(vfs, f, b, checkambig):
116 117 filepath = vfs.join(f)
117 118 backuppath = vfs.join(b)
118 119 try:
119 120 util.copyfile(backuppath, filepath, checkambig=checkambig)
120 121 backupfiles.append((vfs, b))
121 122 except IOError as exc:
122 123 e_msg = stringutil.forcebytestr(exc)
123 124 report(_(b"failed to recover %s (%s)\n") % (f, e_msg))
124 125 raise
125 126
126 127 # gather all backup files that impact the store
127 128 # (we need this to detect files that are both backed up and truncated)
128 129 store_backup = {}
129 130 for entry in backupentries:
130 131 location, file_path, backup_path, cache = entry
131 132 vfs = vfsmap[location]
132 133 is_store = vfs.join(b'') == opener.join(b'')
133 134 if is_store and file_path and backup_path:
134 135 store_backup[file_path] = entry
135 136 copy_done = set()
136 137
137 138 # truncate all file `f` to offset `o`
138 139 for f, o in sorted(dict(entries).items()):
139 140 # if we have a backup for `f`, we should restore it first and truncate
140 141 # the restored file
141 142 bck_entry = store_backup.get(f)
142 143 if bck_entry is not None:
143 144 location, file_path, backup_path, cache = bck_entry
144 145 checkambig = False
145 146 if checkambigfiles:
146 147 checkambig = (file_path, location) in checkambigfiles
147 148 restore_one_backup(opener, file_path, backup_path, checkambig)
148 149 copy_done.add(bck_entry)
149 150 # truncate the file to its pre-transaction size
150 151 if o or not unlink:
151 152 checkambig = checkambigfiles and (f, b'') in checkambigfiles
152 153 try:
153 154 fp = opener(f, b'a', checkambig=checkambig)
154 155 if fp.tell() < o:
155 156 raise error.Abort(
156 157 _(
157 158 b"attempted to truncate %s to %d bytes, but it was "
158 159 b"already %d bytes\n"
159 160 )
160 161 % (f, o, fp.tell())
161 162 )
162 163 fp.truncate(o)
163 164 fp.close()
164 165 except IOError:
165 166 report(_(b"failed to truncate %s\n") % f)
166 167 raise
167 168 else:
168 169 # delete empty file
169 170 try:
170 171 opener.unlink(f)
171 172 except FileNotFoundError:
172 173 pass
173 174 # restore backed up files and clean up temporary files
174 175 for entry in backupentries:
175 176 if entry in copy_done:
176 177 continue
177 178 l, f, b, c = entry
178 179 if l not in vfsmap and c:
179 180 report(b"couldn't handle %s: unknown cache location %s\n" % (b, l))
180 181 vfs = vfsmap[l]
181 182 try:
182 183 checkambig = checkambigfiles and (f, l) in checkambigfiles
183 184 if f and b:
184 185 restore_one_backup(vfs, f, b, checkambig)
185 186 else:
186 187 target = f or b
187 188 try:
188 189 vfs.unlink(target)
189 190 except FileNotFoundError:
190 191 # This is fine because
191 192 #
192 193 # either we are trying to delete the main file, and it is
193 194 # already deleted.
194 195 #
195 196 # or we are trying to delete a temporary file and it is
196 197 # already deleted.
197 198 #
198 199 # in both case, our target result (delete the file) is
199 200 # already achieved.
200 201 pass
201 202 except (IOError, OSError, error.Abort):
202 203 if not c:
203 204 raise
204 205
205 206 # cleanup transaction state file and the backups file
206 207 backuppath = b"%s.backupfiles" % journal
207 208 if opener.exists(backuppath):
208 209 opener.unlink(backuppath)
209 210 opener.unlink(journal)
210 211 try:
211 212 for vfs, f in backupfiles:
212 213 if vfs.exists(f):
213 214 vfs.unlink(f)
214 215 except (IOError, OSError, error.Abort):
215 216 # only pure backup file remains, it is sage to ignore any error
216 217 pass
217 218
218 219
219 220 class transaction(util.transactional):
220 221 def __init__(
221 222 self,
222 223 report,
223 224 opener,
224 225 vfsmap,
225 226 journalname,
226 227 undoname=None,
227 228 after=None,
228 229 createmode=None,
229 230 validator=None,
230 231 releasefn=None,
231 232 checkambigfiles=None,
232 name='<unnamed>',
233 name=b'<unnamed>',
233 234 ):
234 235 """Begin a new transaction
235 236
236 237 Begins a new transaction that allows rolling back writes in the event of
237 238 an exception.
238 239
239 240 * `after`: called after the transaction has been committed
240 241 * `createmode`: the mode of the journal file that will be created
241 242 * `releasefn`: called after releasing (with transaction and result)
242 243
243 244 `checkambigfiles` is a set of (path, vfs-location) tuples,
244 245 which determine whether file stat ambiguity should be avoided
245 246 for corresponded files.
246 247 """
247 248 self._count = 1
248 249 self._usages = 1
249 250 self._report = report
250 251 # a vfs to the store content
251 252 self._opener = opener
252 253 # a map to access file in various {location -> vfs}
253 254 vfsmap = vfsmap.copy()
254 255 vfsmap[b''] = opener # set default value
255 256 self._vfsmap = vfsmap
256 257 self._after = after
257 258 self._offsetmap = {}
258 259 self._newfiles = set()
259 260 self._journal = journalname
260 261 self._journal_files = []
261 262 self._undoname = undoname
262 263 self._queue = []
263 264 # A callback to do something just after releasing transaction.
264 265 if releasefn is None:
265 266 releasefn = lambda tr, success: None
266 267 self._releasefn = releasefn
267 268
268 269 self._checkambigfiles = set()
269 270 if checkambigfiles:
270 271 self._checkambigfiles.update(checkambigfiles)
271 272
272 273 self._names = [name]
273 274
274 275 # A dict dedicated to precisely tracking the changes introduced in the
275 276 # transaction.
276 277 self.changes = {}
277 278
278 279 # a dict of arguments to be passed to hooks
279 280 self.hookargs = {}
280 281 self._file = opener.open(self._journal, b"w+")
281 282
282 283 # a list of ('location', 'path', 'backuppath', cache) entries.
283 284 # - if 'backuppath' is empty, no file existed at backup time
284 285 # - if 'path' is empty, this is a temporary transaction file
285 286 # - if 'location' is not empty, the path is outside main opener reach.
286 287 # use 'location' value as a key in a vfsmap to find the right 'vfs'
287 288 # (cache is currently unused)
288 289 self._backupentries = []
289 290 self._backupmap = {}
290 291 self._backupjournal = b"%s.backupfiles" % self._journal
291 292 self._backupsfile = opener.open(self._backupjournal, b'w')
292 293 self._backupsfile.write(b'%d\n' % version)
293 294 # the set of temporary files
294 295 self._tmp_files = set()
295 296
296 297 if createmode is not None:
297 298 opener.chmod(self._journal, createmode & 0o666)
298 299 opener.chmod(self._backupjournal, createmode & 0o666)
299 300
300 301 # hold file generations to be performed on commit
301 302 self._filegenerators = {}
302 303 # hold callback to write pending data for hooks
303 304 self._pendingcallback = {}
304 305 # True is any pending data have been written ever
305 306 self._anypending = False
306 307 # holds callback to call when writing the transaction
307 308 self._finalizecallback = {}
308 309 # holds callback to call when validating the transaction
309 310 # should raise exception if anything is wrong
310 311 self._validatecallback = {}
311 312 if validator is not None:
312 313 self._validatecallback[b'001-userhooks'] = validator
313 314 # hold callback for post transaction close
314 315 self._postclosecallback = {}
315 316 # holds callbacks to call during abort
316 317 self._abortcallback = {}
317 318
318 319 def __repr__(self):
319 320 name = b'/'.join(self._names)
320 321 return '<transaction name=%s, count=%d, usages=%d>' % (
321 name,
322 encoding.strfromlocal(name),
322 323 self._count,
323 324 self._usages,
324 325 )
325 326
326 327 def __del__(self):
327 328 if self._journal:
328 329 self._abort()
329 330
330 331 @property
331 332 def finalized(self):
332 333 return self._finalizecallback is None
333 334
334 335 @active
335 336 def startgroup(self):
336 337 """delay registration of file entry
337 338
338 339 This is used by strip to delay vision of strip offset. The transaction
339 340 sees either none or all of the strip actions to be done."""
340 341 self._queue.append([])
341 342
342 343 @active
343 344 def endgroup(self):
344 345 """apply delayed registration of file entry.
345 346
346 347 This is used by strip to delay vision of strip offset. The transaction
347 348 sees either none or all of the strip actions to be done."""
348 349 q = self._queue.pop()
349 350 for f, o in q:
350 351 self._addentry(f, o)
351 352
352 353 @active
353 354 def add(self, file, offset):
354 355 """record the state of an append-only file before update"""
355 356 if (
356 357 file in self._newfiles
357 358 or file in self._offsetmap
358 359 or file in self._backupmap
359 360 or file in self._tmp_files
360 361 ):
361 362 return
362 363 if self._queue:
363 364 self._queue[-1].append((file, offset))
364 365 return
365 366
366 367 self._addentry(file, offset)
367 368
368 369 def _addentry(self, file, offset):
369 370 """add a append-only entry to memory and on-disk state"""
370 371 if (
371 372 file in self._newfiles
372 373 or file in self._offsetmap
373 374 or file in self._backupmap
374 375 or file in self._tmp_files
375 376 ):
376 377 return
377 378 if offset:
378 379 self._offsetmap[file] = offset
379 380 else:
380 381 self._newfiles.add(file)
381 382 # add enough data to the journal to do the truncate
382 383 self._file.write(b"%s\0%d\n" % (file, offset))
383 384 self._file.flush()
384 385
385 386 @active
386 387 def addbackup(self, file, hardlink=True, location=b'', for_offset=False):
387 388 """Adds a backup of the file to the transaction
388 389
389 390 Calling addbackup() creates a hardlink backup of the specified file
390 391 that is used to recover the file in the event of the transaction
391 392 aborting.
392 393
393 394 * `file`: the file path, relative to .hg/store
394 395 * `hardlink`: use a hardlink to quickly create the backup
395 396
396 397 If `for_offset` is set, we expect a offset for this file to have been previously recorded
397 398 """
398 399 if self._queue:
399 400 msg = b'cannot use transaction.addbackup inside "group"'
400 401 raise error.ProgrammingError(msg)
401 402
402 403 if file in self._newfiles or file in self._backupmap:
403 404 return
404 405 elif file in self._offsetmap and not for_offset:
405 406 return
406 407 elif for_offset and file not in self._offsetmap:
407 408 msg = (
408 409 'calling `addbackup` with `for_offmap=True`, '
409 410 'but no offset recorded: [%r] %r'
410 411 )
411 412 msg %= (location, file)
412 413 raise error.ProgrammingError(msg)
413 414
414 415 vfs = self._vfsmap[location]
415 416 dirname, filename = vfs.split(file)
416 417 backupfilename = b"%s.backup.%s.bck" % (self._journal, filename)
417 418 backupfile = vfs.reljoin(dirname, backupfilename)
418 419 if vfs.exists(file):
419 420 filepath = vfs.join(file)
420 421 backuppath = vfs.join(backupfile)
421 422 # store encoding may result in different directory here.
422 423 # so we have to ensure the destination directory exist
423 424 final_dir_name = os.path.dirname(backuppath)
424 425 util.makedirs(final_dir_name, mode=vfs.createmode, notindexed=True)
425 426 # then we can copy the backup
426 427 util.copyfile(filepath, backuppath, hardlink=hardlink)
427 428 else:
428 429 backupfile = b''
429 430
430 431 self._addbackupentry((location, file, backupfile, False))
431 432
432 433 def _addbackupentry(self, entry):
433 434 """register a new backup entry and write it to disk"""
434 435 self._backupentries.append(entry)
435 436 self._backupmap[entry[1]] = len(self._backupentries) - 1
436 437 self._backupsfile.write(b"%s\0%s\0%s\0%d\n" % entry)
437 438 self._backupsfile.flush()
438 439
439 440 @active
440 441 def registertmp(self, tmpfile, location=b''):
441 442 """register a temporary transaction file
442 443
443 444 Such files will be deleted when the transaction exits (on both
444 445 failure and success).
445 446 """
446 447 self._tmp_files.add(tmpfile)
447 448 self._addbackupentry((location, b'', tmpfile, False))
448 449
449 450 @active
450 451 def addfilegenerator(
451 452 self,
452 453 genid,
453 454 filenames,
454 455 genfunc,
455 456 order=0,
456 457 location=b'',
457 458 post_finalize=False,
458 459 ):
459 460 """add a function to generates some files at transaction commit
460 461
461 462 The `genfunc` argument is a function capable of generating proper
462 463 content of each entry in the `filename` tuple.
463 464
464 465 At transaction close time, `genfunc` will be called with one file
465 466 object argument per entries in `filenames`.
466 467
467 468 The transaction itself is responsible for the backup, creation and
468 469 final write of such file.
469 470
470 471 The `genid` argument is used to ensure the same set of file is only
471 472 generated once. Call to `addfilegenerator` for a `genid` already
472 473 present will overwrite the old entry.
473 474
474 475 The `order` argument may be used to control the order in which multiple
475 476 generator will be executed.
476 477
477 478 The `location` arguments may be used to indicate the files are located
478 479 outside of the the standard directory for transaction. It should match
479 480 one of the key of the `transaction.vfsmap` dictionary.
480 481
481 482 The `post_finalize` argument can be set to `True` for file generation
482 483 that must be run after the transaction has been finalized.
483 484 """
484 485 # For now, we are unable to do proper backup and restore of custom vfs
485 486 # but for bookmarks that are handled outside this mechanism.
486 487 entry = (order, filenames, genfunc, location, post_finalize)
487 488 self._filegenerators[genid] = entry
488 489
489 490 @active
490 491 def removefilegenerator(self, genid):
491 492 """reverse of addfilegenerator, remove a file generator function"""
492 493 if genid in self._filegenerators:
493 494 del self._filegenerators[genid]
494 495
495 496 def _generatefiles(self, suffix=b'', group=GEN_GROUP_ALL):
496 497 # write files registered for generation
497 498 any = False
498 499
499 500 if group == GEN_GROUP_ALL:
500 501 skip_post = skip_pre = False
501 502 else:
502 503 skip_pre = group == GEN_GROUP_POST_FINALIZE
503 504 skip_post = group == GEN_GROUP_PRE_FINALIZE
504 505
505 506 for id, entry in sorted(self._filegenerators.items()):
506 507 any = True
507 508 order, filenames, genfunc, location, post_finalize = entry
508 509
509 510 # for generation at closing, check if it's before or after finalize
510 511 if skip_post and post_finalize:
511 512 continue
512 513 elif skip_pre and not post_finalize:
513 514 continue
514 515
515 516 vfs = self._vfsmap[location]
516 517 files = []
517 518 try:
518 519 for name in filenames:
519 520 name += suffix
520 521 if suffix:
521 522 self.registertmp(name, location=location)
522 523 checkambig = False
523 524 else:
524 525 self.addbackup(name, location=location)
525 526 checkambig = (name, location) in self._checkambigfiles
526 527 files.append(
527 528 vfs(name, b'w', atomictemp=True, checkambig=checkambig)
528 529 )
529 530 genfunc(*files)
530 531 for f in files:
531 532 f.close()
532 533 # skip discard() loop since we're sure no open file remains
533 534 del files[:]
534 535 finally:
535 536 for f in files:
536 537 f.discard()
537 538 return any
538 539
539 540 @active
540 541 def findoffset(self, file):
541 542 if file in self._newfiles:
542 543 return 0
543 544 return self._offsetmap.get(file)
544 545
545 546 @active
546 547 def readjournal(self):
547 548 self._file.seek(0)
548 549 entries = []
549 550 for l in self._file.readlines():
550 551 file, troffset = l.split(b'\0')
551 552 entries.append((file, int(troffset)))
552 553 return entries
553 554
554 555 @active
555 556 def replace(self, file, offset):
556 557 """
557 558 replace can only replace already committed entries
558 559 that are not pending in the queue
559 560 """
560 561 if file in self._newfiles:
561 562 if not offset:
562 563 return
563 564 self._newfiles.remove(file)
564 565 self._offsetmap[file] = offset
565 566 elif file in self._offsetmap:
566 567 if not offset:
567 568 del self._offsetmap[file]
568 569 self._newfiles.add(file)
569 570 else:
570 571 self._offsetmap[file] = offset
571 572 else:
572 573 raise KeyError(file)
573 574 self._file.write(b"%s\0%d\n" % (file, offset))
574 575 self._file.flush()
575 576
576 577 @active
577 def nest(self, name='<unnamed>'):
578 def nest(self, name=b'<unnamed>'):
578 579 self._count += 1
579 580 self._usages += 1
580 581 self._names.append(name)
581 582 return self
582 583
583 584 def release(self):
584 585 if self._count > 0:
585 586 self._usages -= 1
586 587 if self._names:
587 588 self._names.pop()
588 589 # if the transaction scopes are left without being closed, fail
589 590 if self._count > 0 and self._usages == 0:
590 591 self._abort()
591 592
592 593 def running(self):
593 594 return self._count > 0
594 595
595 596 def addpending(self, category, callback):
596 597 """add a callback to be called when the transaction is pending
597 598
598 599 The transaction will be given as callback's first argument.
599 600
600 601 Category is a unique identifier to allow overwriting an old callback
601 602 with a newer callback.
602 603 """
603 604 self._pendingcallback[category] = callback
604 605
605 606 @active
606 607 def writepending(self):
607 608 """write pending file to temporary version
608 609
609 610 This is used to allow hooks to view a transaction before commit"""
610 611 categories = sorted(self._pendingcallback)
611 612 for cat in categories:
612 613 # remove callback since the data will have been flushed
613 614 any = self._pendingcallback.pop(cat)(self)
614 615 self._anypending = self._anypending or any
615 616 self._anypending |= self._generatefiles(suffix=b'.pending')
616 617 return self._anypending
617 618
618 619 @active
619 620 def hasfinalize(self, category):
620 621 """check is a callback already exist for a category"""
621 622 return category in self._finalizecallback
622 623
623 624 @active
624 625 def addfinalize(self, category, callback):
625 626 """add a callback to be called when the transaction is closed
626 627
627 628 The transaction will be given as callback's first argument.
628 629
629 630 Category is a unique identifier to allow overwriting old callbacks with
630 631 newer callbacks.
631 632 """
632 633 self._finalizecallback[category] = callback
633 634
634 635 @active
635 636 def addpostclose(self, category, callback):
636 637 """add or replace a callback to be called after the transaction closed
637 638
638 639 The transaction will be given as callback's first argument.
639 640
640 641 Category is a unique identifier to allow overwriting an old callback
641 642 with a newer callback.
642 643 """
643 644 self._postclosecallback[category] = callback
644 645
645 646 @active
646 647 def getpostclose(self, category):
647 648 """return a postclose callback added before, or None"""
648 649 return self._postclosecallback.get(category, None)
649 650
650 651 @active
651 652 def addabort(self, category, callback):
652 653 """add a callback to be called when the transaction is aborted.
653 654
654 655 The transaction will be given as the first argument to the callback.
655 656
656 657 Category is a unique identifier to allow overwriting an old callback
657 658 with a newer callback.
658 659 """
659 660 self._abortcallback[category] = callback
660 661
661 662 @active
662 663 def addvalidator(self, category, callback):
663 664 """adds a callback to be called when validating the transaction.
664 665
665 666 The transaction will be given as the first argument to the callback.
666 667
667 668 callback should raise exception if to abort transaction"""
668 669 self._validatecallback[category] = callback
669 670
670 671 @active
671 672 def close(self):
672 673 '''commit the transaction'''
673 674 if self._count == 1:
674 675 for category in sorted(self._validatecallback):
675 676 self._validatecallback[category](self)
676 677 self._validatecallback = None # Help prevent cycles.
677 678 self._generatefiles(group=GEN_GROUP_PRE_FINALIZE)
678 679 while self._finalizecallback:
679 680 callbacks = self._finalizecallback
680 681 self._finalizecallback = {}
681 682 categories = sorted(callbacks)
682 683 for cat in categories:
683 684 callbacks[cat](self)
684 685 # Prevent double usage and help clear cycles.
685 686 self._finalizecallback = None
686 687 self._generatefiles(group=GEN_GROUP_POST_FINALIZE)
687 688
688 689 self._count -= 1
689 690 if self._count != 0:
690 691 return
691 692 self._file.close()
692 693 self._backupsfile.close()
693 694 # cleanup temporary files
694 695 for l, f, b, c in self._backupentries:
695 696 if l not in self._vfsmap and c:
696 697 self._report(
697 698 b"couldn't remove %s: unknown cache location %s\n" % (b, l)
698 699 )
699 700 continue
700 701 vfs = self._vfsmap[l]
701 702 if not f and b and vfs.exists(b):
702 703 try:
703 704 vfs.unlink(b)
704 705 except (IOError, OSError, error.Abort) as inst:
705 706 if not c:
706 707 raise
707 708 # Abort may be raise by read only opener
708 709 self._report(
709 710 b"couldn't remove %s: %s\n" % (vfs.join(b), inst)
710 711 )
711 712 self._offsetmap = {}
712 713 self._newfiles = set()
713 714 self._writeundo()
714 715 if self._after:
715 716 self._after()
716 717 self._after = None # Help prevent cycles.
717 718 if self._opener.isfile(self._backupjournal):
718 719 self._opener.unlink(self._backupjournal)
719 720 if self._opener.isfile(self._journal):
720 721 self._opener.unlink(self._journal)
721 722 for l, _f, b, c in self._backupentries:
722 723 if l not in self._vfsmap and c:
723 724 self._report(
724 725 b"couldn't remove %s: unknown cache location"
725 726 b"%s\n" % (b, l)
726 727 )
727 728 continue
728 729 vfs = self._vfsmap[l]
729 730 if b and vfs.exists(b):
730 731 try:
731 732 vfs.unlink(b)
732 733 except (IOError, OSError, error.Abort) as inst:
733 734 if not c:
734 735 raise
735 736 # Abort may be raise by read only opener
736 737 self._report(
737 738 b"couldn't remove %s: %s\n" % (vfs.join(b), inst)
738 739 )
739 740 self._backupentries = []
740 741 self._journal = None
741 742
742 743 self._releasefn(self, True) # notify success of closing transaction
743 744 self._releasefn = None # Help prevent cycles.
744 745
745 746 # run post close action
746 747 categories = sorted(self._postclosecallback)
747 748 for cat in categories:
748 749 self._postclosecallback[cat](self)
749 750 # Prevent double usage and help clear cycles.
750 751 self._postclosecallback = None
751 752
752 753 @active
753 754 def abort(self):
754 755 """abort the transaction (generally called on error, or when the
755 756 transaction is not explicitly committed before going out of
756 757 scope)"""
757 758 self._abort()
758 759
759 760 @active
760 761 def add_journal(self, vfs_id, path):
761 762 self._journal_files.append((vfs_id, path))
762 763
763 764 def _writeundo(self):
764 765 """write transaction data for possible future undo call"""
765 766 if self._undoname is None:
766 767 return
767 768 cleanup_undo_files(
768 769 self._report,
769 770 self._vfsmap,
770 771 undo_prefix=self._undoname,
771 772 )
772 773
773 774 def undoname(fn: bytes) -> bytes:
774 775 base, name = os.path.split(fn)
775 776 assert name.startswith(self._journal)
776 777 new_name = name.replace(self._journal, self._undoname, 1)
777 778 return os.path.join(base, new_name)
778 779
779 780 undo_backup_path = b"%s.backupfiles" % self._undoname
780 781 undobackupfile = self._opener.open(undo_backup_path, b'w')
781 782 undobackupfile.write(b'%d\n' % version)
782 783 for l, f, b, c in self._backupentries:
783 784 if not f: # temporary file
784 785 continue
785 786 if not b:
786 787 u = b''
787 788 else:
788 789 if l not in self._vfsmap and c:
789 790 self._report(
790 791 b"couldn't remove %s: unknown cache location"
791 792 b"%s\n" % (b, l)
792 793 )
793 794 continue
794 795 vfs = self._vfsmap[l]
795 796 u = undoname(b)
796 797 util.copyfile(vfs.join(b), vfs.join(u), hardlink=True)
797 798 undobackupfile.write(b"%s\0%s\0%s\0%d\n" % (l, f, u, c))
798 799 undobackupfile.close()
799 800 for vfs, src in self._journal_files:
800 801 dest = undoname(src)
801 802 # if src and dest refer to a same file, vfs.rename is a no-op,
802 803 # leaving both src and dest on disk. delete dest to make sure
803 804 # the rename couldn't be such a no-op.
804 805 vfs.tryunlink(dest)
805 806 try:
806 807 vfs.rename(src, dest)
807 808 except FileNotFoundError: # journal file does not yet exist
808 809 pass
809 810
810 811 def _abort(self):
811 812 entries = self.readjournal()
812 813 self._count = 0
813 814 self._usages = 0
814 815 self._file.close()
815 816 self._backupsfile.close()
816 817
817 818 quick = self._can_quick_abort(entries)
818 819 try:
819 820 if not quick:
820 821 self._report(_(b"transaction abort!\n"))
821 822 for cat in sorted(self._abortcallback):
822 823 self._abortcallback[cat](self)
823 824 # Prevent double usage and help clear cycles.
824 825 self._abortcallback = None
825 826 if quick:
826 827 self._do_quick_abort(entries)
827 828 else:
828 829 self._do_full_abort(entries)
829 830 finally:
830 831 self._journal = None
831 832 self._releasefn(self, False) # notify failure of transaction
832 833 self._releasefn = None # Help prevent cycles.
833 834
834 835 def _can_quick_abort(self, entries):
835 836 """False if any semantic content have been written on disk
836 837
837 838 True if nothing, except temporary files has been writen on disk."""
838 839 if entries:
839 840 return False
840 841 for e in self._backupentries:
841 842 if e[1]:
842 843 return False
843 844 return True
844 845
845 846 def _do_quick_abort(self, entries):
846 847 """(Silently) do a quick cleanup (see _can_quick_abort)"""
847 848 assert self._can_quick_abort(entries)
848 849 tmp_files = [e for e in self._backupentries if not e[1]]
849 850 for vfs_id, old_path, tmp_path, xxx in tmp_files:
850 851 vfs = self._vfsmap[vfs_id]
851 852 try:
852 853 vfs.unlink(tmp_path)
853 854 except FileNotFoundError:
854 855 pass
855 856 if self._backupjournal:
856 857 self._opener.unlink(self._backupjournal)
857 858 if self._journal:
858 859 self._opener.unlink(self._journal)
859 860
860 861 def _do_full_abort(self, entries):
861 862 """(Noisily) rollback all the change introduced by the transaction"""
862 863 try:
863 864 _playback(
864 865 self._journal,
865 866 self._report,
866 867 self._opener,
867 868 self._vfsmap,
868 869 entries,
869 870 self._backupentries,
870 871 unlink=True,
871 872 checkambigfiles=self._checkambigfiles,
872 873 )
873 874 self._report(_(b"rollback completed\n"))
874 875 except BaseException as exc:
875 876 self._report(_(b"rollback failed - please run hg recover\n"))
876 877 self._report(
877 878 _(b"(failure reason: %s)\n") % stringutil.forcebytestr(exc)
878 879 )
879 880
880 881
881 882 BAD_VERSION_MSG = _(
882 883 b"journal was created by a different version of Mercurial\n"
883 884 )
884 885
885 886
886 887 def read_backup_files(report, fp):
887 888 """parse an (already open) backup file an return contained backup entries
888 889
889 890 entries are in the form: (location, file, backupfile, xxx)
890 891
891 892 :location: the vfs identifier (vfsmap's key)
892 893 :file: original file path (in the vfs)
893 894 :backupfile: path of the backup (in the vfs)
894 895 :cache: a boolean currently always set to False
895 896 """
896 897 lines = fp.readlines()
897 898 backupentries = []
898 899 if lines:
899 900 ver = lines[0][:-1]
900 901 if ver != (b'%d' % version):
901 902 report(BAD_VERSION_MSG)
902 903 else:
903 904 for line in lines[1:]:
904 905 if line:
905 906 # Shave off the trailing newline
906 907 line = line[:-1]
907 908 l, f, b, c = line.split(b'\0')
908 909 backupentries.append((l, f, b, bool(c)))
909 910 return backupentries
910 911
911 912
912 913 def rollback(
913 914 opener,
914 915 vfsmap,
915 916 file,
916 917 report,
917 918 checkambigfiles=None,
918 919 skip_journal_pattern=None,
919 920 ):
920 921 """Rolls back the transaction contained in the given file
921 922
922 923 Reads the entries in the specified file, and the corresponding
923 924 '*.backupfiles' file, to recover from an incomplete transaction.
924 925
925 926 * `file`: a file containing a list of entries, specifying where
926 927 to truncate each file. The file should contain a list of
927 928 file\0offset pairs, delimited by newlines. The corresponding
928 929 '*.backupfiles' file should contain a list of file\0backupfile
929 930 pairs, delimited by \0.
930 931
931 932 `checkambigfiles` is a set of (path, vfs-location) tuples,
932 933 which determine whether file stat ambiguity should be avoided at
933 934 restoring corresponded files.
934 935 """
935 936 entries = []
936 937 backupentries = []
937 938
938 939 with opener.open(file) as fp:
939 940 lines = fp.readlines()
940 941 for l in lines:
941 942 try:
942 943 f, o = l.split(b'\0')
943 944 entries.append((f, int(o)))
944 945 except ValueError:
945 946 report(
946 947 _(b"couldn't read journal entry %r!\n") % pycompat.bytestr(l)
947 948 )
948 949
949 950 backupjournal = b"%s.backupfiles" % file
950 951 if opener.exists(backupjournal):
951 952 with opener.open(backupjournal) as fp:
952 953 backupentries = read_backup_files(report, fp)
953 954 if skip_journal_pattern is not None:
954 955 keep = lambda x: not skip_journal_pattern.match(x[1])
955 956 backupentries = [x for x in backupentries if keep(x)]
956 957
957 958 _playback(
958 959 file,
959 960 report,
960 961 opener,
961 962 vfsmap,
962 963 entries,
963 964 backupentries,
964 965 checkambigfiles=checkambigfiles,
965 966 )
@@ -1,99 +1,109 b''
1 = Mercurial 6.5.2 =
2
3 * hgweb: encode WSGI environment using the ISO-8859-1 codec
4 * rhg: fix the bug where sparse config is interpreted as relglob instead of glob
5 * gpg: fix an UnboundLocalError whenever using --force
6 * transaction: fix __repr__() and make the default name bytes
7 * setup: make the error "Unable to find a working hg binary" more informative
8 * tests: avoid test environment affecting setup.py
9 * run-tests: detect HGWITHRUSTEXT value
10
1 11 = Mercurial 6.5.1 =
2 12
3 13 * A bunch of improvements to Python 3.12 compatibility
4 14 * repoview: fix the filter created by `extrafilter`
5 15 * Improve portability of the test suite
6 16 * fncache: fix a bug that corrupts the fncache after transaction rollback
7 17 * revlog: fix the naming scheme use by split temporary file
8 18 * perf: fix perf::tags
9 19
10 20 = Mercurial 6.5 =
11 21
12 22 As usual, a lot of patches don't make it to this list since they're more internal.
13 23
14 24 == New Features ==
15 25
16 26 * Improved Python 3.12 compatiblity
17 27 * configitems: enable changegroup3 by default (unless using infinitepush)
18 28 * extras: expose 'retained_extras' for extensions to extend
19 29 * stabletailgraph: implement stable-tail sort
20 30 * stabletailgraph: naive version of leap computation
21 31 * bundle: introduce a "v3" spec
22 32 * clone-bundles: add a basic first version of automatic bundle generation
23 33 * clone-bundles: garbage collect older bundle when generating new ones
24 34 * clone-bundles: only regenerate the clone bundle when cached ration is low
25 35 * clone-bundles: also control automation based on absolute number of revisions
26 36 * clone-bundles: add a configuration to control auto-generation on changes
27 37 * clone-bundles: introduce a command to refresh bundle
28 38 * clone-bundles: add a command to clear all bundles
29 39 * clone-bundles: add an option to generate bundles in the background
30 40 * clonebundles: add support for inline (streaming) clonebundles
31 41 * clonebundles: adds a auto-generate.serve-inline option
32 42 * match: add `filepath:` pattern to match an exact filepath relative to the root
33 43 * hgweb: add "children" into the JSON template for a changeset
34 44 * hgweb: add support to explicitly access hidden changesets
35 45 * pull: add --remote-hidden option and pass it through peer creation
36 46 * hidden: add support for --remote-hidden to HTTP peer
37 47 * hidden: support passing --hidden with `serve --stdio`
38 48 * hidden: add support to explicitly access hidden changesets with SSH peers
39 49 * perf: introduce a `perf::stream-locked-section` command
40 50 * perf: add a function to find a stream version generator
41 51 * perf: add support for stream-v3 during benchmark
42 52 * perf: add a perf::stream-generate command
43 53 * perf: add a perf::stream-consume
44 54 * cli: make debugnodemap capable of inspecting an arbitrary nodemap
45 55 * rust: configure MSRV in Clippy
46 56 * rhg: make `rhg files` work if `ui.relative-files=true` is specified
47 57 * rhg: support `rhg files` with `ui.relative-paths=false`
48 58 * rhg: support `status --print0`
49 59 * tree-manifest: allow `debugupgraderepo` to run on tree manifest repo
50 60 * library: enable runpy invocation on mercurial package
51 61 * library: incorporate demandimport into runpy invocation
52 62 * exchange: allow passing no includes/excludes to `pull()`
53 63
54 64 == New Experimental Features ==
55 65
56 66 * stream-clone: add an experimental v3 version of the protocol
57 67 * stream-clone: support streamv3 on the cli [hg bundle]
58 68
59 69 == Bug Fixes ==
60 70
61 71 * mail: add a missing argument to properly override starttls
62 72 * bundle: include required phases when saving a bundle (issue6794)
63 73 * outgoing: fix common-heads computation from `missingroots` argument
64 74 * strip: do not include internal changeset in the strip backup
65 75 * bundle: abort if the user request bundling of internal changesets
66 76 * bundle: prevent implicit bundling of internal changeset
67 77 * encoding: avoid quadratic time complexity when json-encoding non-UTF8 strings
68 78 * sha1dc: Make sure SHA1DC_BIGENDIAN is set on Darwin/PowerPC
69 79 * zstd: hack include order to ensure that our zstd.h is found
70 80 * dirstate: better error messages when dirstate is corrupted
71 81 * stream-clone: avoid opening a revlog in case we do not need it
72 82 * treemanifest: make `updatecaches` update the nodemaps for all directories
73 83 * rust-hg-core: move from `ouroboros` to `self_cell`
74 84 * rust-dependencies: switch from `users` to `whoami`
75 85 * dirstate-v2: actually fix the dirstate-v2 upgrade race
76 86 * dirstate: avoid leaking disk space in `hg debugrebuilddirstate`
77 87 * clonebundles: add warning if auto-generate is enabled without formats
78 88 * win32mbcs: unbyteify some strings for py3 support
79 89 * rust-revlog: fix incorrect results with NULL_NODE prefixes
80 90 * rust-revlog: fix RevlogEntry.data() for NULL_REVISION
81 91
82 92 == Backwards Compatibility Changes ==
83 93
84 94 * infinitepush: aggressively deprecated infinite push
85 95 * narrow: indicated the default of 'Yes' when confirming auto-remove-includes
86 96
87 97 == Internal API Changes ==
88 98
89 99 * Store walk was reworked to fix small race conditions in stream-clone and
90 100 greatly improve its API robustness and flexibility.
91 101
92 102 == Miscellaneous ==
93 103
94 104 * Typechecking support was improved in a lot of places
95 105 * Removed more useless compat code for now unsupported Python versions
96 106 * Sped up zstd usage in Rust contexts
97 107 * revlog: add an exception hint when processing LFS flags without the extension
98 108 * ui: keep the progress bar around when writing if stdout is not a tty
99 109 * transaction: use a ".bck" extension for all backup file
@@ -1,1808 +1,1824 b''
1 1 #
2 2 # This is the mercurial setup script.
3 3 #
4 4 # 'python setup.py install', or
5 5 # 'python setup.py --help' for more options
6 6 import os
7 7
8 8 # Mercurial can't work on 3.6.0 or 3.6.1 due to a bug in % formatting
9 9 # in bytestrings.
10 10 supportedpy = ','.join(
11 11 [
12 12 '>=3.6.2',
13 13 ]
14 14 )
15 15
16 16 import sys, platform
17 17 import sysconfig
18 18
19 19
20 20 def sysstr(s):
21 21 return s.decode('latin-1')
22 22
23 23
24 24 def eprint(*args, **kwargs):
25 25 kwargs['file'] = sys.stderr
26 26 print(*args, **kwargs)
27 27
28 28
29 29 import ssl
30 30
31 31 # ssl.HAS_TLSv1* are preferred to check support but they were added in Python
32 32 # 3.7. Prior to CPython commit 6e8cda91d92da72800d891b2fc2073ecbc134d98
33 33 # (backported to the 3.7 branch), ssl.PROTOCOL_TLSv1_1 / ssl.PROTOCOL_TLSv1_2
34 34 # were defined only if compiled against a OpenSSL version with TLS 1.1 / 1.2
35 35 # support. At the mentioned commit, they were unconditionally defined.
36 36 _notset = object()
37 37 has_tlsv1_1 = getattr(ssl, 'HAS_TLSv1_1', _notset)
38 38 if has_tlsv1_1 is _notset:
39 39 has_tlsv1_1 = getattr(ssl, 'PROTOCOL_TLSv1_1', _notset) is not _notset
40 40 has_tlsv1_2 = getattr(ssl, 'HAS_TLSv1_2', _notset)
41 41 if has_tlsv1_2 is _notset:
42 42 has_tlsv1_2 = getattr(ssl, 'PROTOCOL_TLSv1_2', _notset) is not _notset
43 43 if not (has_tlsv1_1 or has_tlsv1_2):
44 44 error = """
45 45 The `ssl` module does not advertise support for TLS 1.1 or TLS 1.2.
46 46 Please make sure that your Python installation was compiled against an OpenSSL
47 47 version enabling these features (likely this requires the OpenSSL version to
48 48 be at least 1.0.1).
49 49 """
50 50 print(error, file=sys.stderr)
51 51 sys.exit(1)
52 52
53 53 DYLIB_SUFFIX = sysconfig.get_config_vars()['EXT_SUFFIX']
54 54
55 55 # Solaris Python packaging brain damage
56 56 try:
57 57 import hashlib
58 58
59 59 sha = hashlib.sha1()
60 60 except ImportError:
61 61 try:
62 62 import sha
63 63
64 64 sha.sha # silence unused import warning
65 65 except ImportError:
66 66 raise SystemExit(
67 67 "Couldn't import standard hashlib (incomplete Python install)."
68 68 )
69 69
70 70 try:
71 71 import zlib
72 72
73 73 zlib.compressobj # silence unused import warning
74 74 except ImportError:
75 75 raise SystemExit(
76 76 "Couldn't import standard zlib (incomplete Python install)."
77 77 )
78 78
79 79 # The base IronPython distribution (as of 2.7.1) doesn't support bz2
80 80 isironpython = False
81 81 try:
82 82 isironpython = (
83 83 platform.python_implementation().lower().find("ironpython") != -1
84 84 )
85 85 except AttributeError:
86 86 pass
87 87
88 88 if isironpython:
89 89 sys.stderr.write("warning: IronPython detected (no bz2 support)\n")
90 90 else:
91 91 try:
92 92 import bz2
93 93
94 94 bz2.BZ2Compressor # silence unused import warning
95 95 except ImportError:
96 96 raise SystemExit(
97 97 "Couldn't import standard bz2 (incomplete Python install)."
98 98 )
99 99
100 100 ispypy = "PyPy" in sys.version
101 101
102 102 import ctypes
103 103 import stat, subprocess, time
104 104 import re
105 105 import shutil
106 106 import tempfile
107 107
108 108 # We have issues with setuptools on some platforms and builders. Until
109 109 # those are resolved, setuptools is opt-in except for platforms where
110 110 # we don't have issues.
111 111 issetuptools = os.name == 'nt' or 'FORCE_SETUPTOOLS' in os.environ
112 112 if issetuptools:
113 113 from setuptools import setup
114 114 else:
115 115 try:
116 116 from distutils.core import setup
117 117 except ModuleNotFoundError:
118 118 from setuptools import setup
119 119 from distutils.ccompiler import new_compiler
120 120 from distutils.core import Command, Extension
121 121 from distutils.dist import Distribution
122 122 from distutils.command.build import build
123 123 from distutils.command.build_ext import build_ext
124 124 from distutils.command.build_py import build_py
125 125 from distutils.command.build_scripts import build_scripts
126 126 from distutils.command.install import install
127 127 from distutils.command.install_lib import install_lib
128 128 from distutils.command.install_scripts import install_scripts
129 129 from distutils import log
130 130 from distutils.spawn import spawn, find_executable
131 131 from distutils import file_util
132 132 from distutils.errors import (
133 133 CCompilerError,
134 134 DistutilsError,
135 135 DistutilsExecError,
136 136 )
137 137 from distutils.sysconfig import get_python_inc
138 138
139 139
140 140 def write_if_changed(path, content):
141 141 """Write content to a file iff the content hasn't changed."""
142 142 if os.path.exists(path):
143 143 with open(path, 'rb') as fh:
144 144 current = fh.read()
145 145 else:
146 146 current = b''
147 147
148 148 if current != content:
149 149 with open(path, 'wb') as fh:
150 150 fh.write(content)
151 151
152 152
153 153 scripts = ['hg']
154 154 if os.name == 'nt':
155 155 # We remove hg.bat if we are able to build hg.exe.
156 156 scripts.append('contrib/win32/hg.bat')
157 157
158 158
159 159 def cancompile(cc, code):
160 160 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
161 161 devnull = oldstderr = None
162 162 try:
163 163 fname = os.path.join(tmpdir, 'testcomp.c')
164 164 f = open(fname, 'w')
165 165 f.write(code)
166 166 f.close()
167 167 # Redirect stderr to /dev/null to hide any error messages
168 168 # from the compiler.
169 169 # This will have to be changed if we ever have to check
170 170 # for a function on Windows.
171 171 devnull = open('/dev/null', 'w')
172 172 oldstderr = os.dup(sys.stderr.fileno())
173 173 os.dup2(devnull.fileno(), sys.stderr.fileno())
174 174 objects = cc.compile([fname], output_dir=tmpdir)
175 175 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
176 176 return True
177 177 except Exception:
178 178 return False
179 179 finally:
180 180 if oldstderr is not None:
181 181 os.dup2(oldstderr, sys.stderr.fileno())
182 182 if devnull is not None:
183 183 devnull.close()
184 184 shutil.rmtree(tmpdir)
185 185
186 186
187 187 # simplified version of distutils.ccompiler.CCompiler.has_function
188 188 # that actually removes its temporary files.
189 189 def hasfunction(cc, funcname):
190 190 code = 'int main(void) { %s(); }\n' % funcname
191 191 return cancompile(cc, code)
192 192
193 193
194 194 def hasheader(cc, headername):
195 195 code = '#include <%s>\nint main(void) { return 0; }\n' % headername
196 196 return cancompile(cc, code)
197 197
198 198
199 199 # py2exe needs to be installed to work
200 200 try:
201 201 import py2exe
202 202
203 203 py2exe.patch_distutils()
204 204 py2exeloaded = True
205 205 # import py2exe's patched Distribution class
206 206 from distutils.core import Distribution
207 207 except ImportError:
208 208 py2exeloaded = False
209 209
210 210
211 211 def runcmd(cmd, env, cwd=None):
212 212 p = subprocess.Popen(
213 213 cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, cwd=cwd
214 214 )
215 215 out, err = p.communicate()
216 216 return p.returncode, out, err
217 217
218 218
219 219 class hgcommand:
220 220 def __init__(self, cmd, env):
221 221 self.cmd = cmd
222 222 self.env = env
223 223
224 224 def run(self, args):
225 225 cmd = self.cmd + args
226 226 returncode, out, err = runcmd(cmd, self.env)
227 227 err = filterhgerr(err)
228 228 if err:
229 229 print("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
230 230 print(err, file=sys.stderr)
231 231 if returncode != 0:
232 232 return b''
233 233 return out
234 234
235 235
236 236 def filterhgerr(err):
237 237 # If root is executing setup.py, but the repository is owned by
238 238 # another user (as in "sudo python setup.py install") we will get
239 239 # trust warnings since the .hg/hgrc file is untrusted. That is
240 240 # fine, we don't want to load it anyway. Python may warn about
241 241 # a missing __init__.py in mercurial/locale, we also ignore that.
242 242 err = [
243 243 e
244 244 for e in err.splitlines()
245 245 if (
246 246 not e.startswith(b'not trusting file')
247 247 and not e.startswith(b'warning: Not importing')
248 248 and not e.startswith(b'obsolete feature not enabled')
249 249 and not e.startswith(b'*** failed to import extension')
250 250 and not e.startswith(b'devel-warn:')
251 251 and not (
252 252 e.startswith(b'(third party extension')
253 253 and e.endswith(b'or newer of Mercurial; disabling)')
254 254 )
255 255 )
256 256 ]
257 257 return b'\n'.join(b' ' + e for e in err)
258 258
259 259
260 260 def findhg():
261 261 """Try to figure out how we should invoke hg for examining the local
262 262 repository contents.
263 263
264 264 Returns an hgcommand object."""
265 265 # By default, prefer the "hg" command in the user's path. This was
266 266 # presumably the hg command that the user used to create this repository.
267 267 #
268 268 # This repository may require extensions or other settings that would not
269 269 # be enabled by running the hg script directly from this local repository.
270 270 hgenv = os.environ.copy()
271 271 # Use HGPLAIN to disable hgrc settings that would change output formatting,
272 272 # and disable localization for the same reasons.
273 273 hgenv['HGPLAIN'] = '1'
274 274 hgenv['LANGUAGE'] = 'C'
275 275 hgcmd = ['hg']
276 276 # Run a simple "hg log" command just to see if using hg from the user's
277 277 # path works and can successfully interact with this repository. Windows
278 278 # gives precedence to hg.exe in the current directory, so fall back to the
279 279 # python invocation of local hg, where pythonXY.dll can always be found.
280 280 check_cmd = ['log', '-r.', '-Ttest']
281 if os.name != 'nt' or not os.path.exists("hg.exe"):
281 attempts = []
282
283 def attempt(cmd, env):
282 284 try:
283 285 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
284 except EnvironmentError:
285 retcode = -1
286 if retcode == 0 and not filterhgerr(err):
286 res = (True, retcode, out, err)
287 if retcode == 0 and not filterhgerr(err):
288 return True
289 except EnvironmentError as e:
290 res = (False, e)
291 attempts.append((cmd, res))
292 return False
293
294 if os.name != 'nt' or not os.path.exists("hg.exe"):
295 if attempt(hgcmd + check_cmd, hgenv):
287 296 return hgcommand(hgcmd, hgenv)
288 297
289 298 # Fall back to trying the local hg installation.
290 299 hgenv = localhgenv()
291 300 hgcmd = [sys.executable, 'hg']
292 try:
293 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
294 except EnvironmentError:
295 retcode = -1
296 if retcode == 0 and not filterhgerr(err):
301 if attempt(hgcmd + check_cmd, hgenv):
297 302 return hgcommand(hgcmd, hgenv)
298 303
299 304 eprint("/!\\")
300 305 eprint(r"/!\ Unable to find a working hg binary")
301 eprint(r"/!\ Version cannot be extract from the repository")
306 eprint(r"/!\ Version cannot be extracted from the repository")
302 307 eprint(r"/!\ Re-run the setup once a first version is built")
308 eprint(r"/!\ Attempts:")
309 for i, e in enumerate(attempts):
310 eprint(r"/!\ attempt #%d:" % (i))
311 eprint(r"/!\ cmd: ", e[0])
312 res = e[1]
313 if res[0]:
314 eprint(r"/!\ return code:", res[1])
315 eprint("/!\\ std output:\n%s" % (res[2].decode()), end="")
316 eprint("/!\\ std error:\n%s" % (res[3].decode()), end="")
317 else:
318 eprint(r"/!\ exception: ", res[1])
303 319 return None
304 320
305 321
306 322 def localhgenv():
307 323 """Get an environment dictionary to use for invoking or importing
308 324 mercurial from the local repository."""
309 325 # Execute hg out of this directory with a custom environment which takes
310 326 # care to not use any hgrc files and do no localization.
311 327 env = {
312 328 'HGMODULEPOLICY': 'py',
313 329 'HGRCPATH': '',
314 330 'LANGUAGE': 'C',
315 331 'PATH': '',
316 332 } # make pypi modules that use os.environ['PATH'] happy
317 333 if 'LD_LIBRARY_PATH' in os.environ:
318 334 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
319 335 if 'SystemRoot' in os.environ:
320 336 # SystemRoot is required by Windows to load various DLLs. See:
321 337 # https://bugs.python.org/issue13524#msg148850
322 338 env['SystemRoot'] = os.environ['SystemRoot']
323 339 return env
324 340
325 341
326 342 version = ''
327 343
328 344
329 345 def _try_get_version():
330 346 hg = findhg()
331 347 if hg is None:
332 348 return ''
333 349 hgid = None
334 350 numerictags = []
335 351 cmd = ['log', '-r', '.', '--template', '{tags}\n']
336 352 pieces = sysstr(hg.run(cmd)).split()
337 353 numerictags = [t for t in pieces if t[0:1].isdigit()]
338 354 hgid = sysstr(hg.run(['id', '-i'])).strip()
339 355 if hgid.count('+') == 2:
340 356 hgid = hgid.replace("+", ".", 1)
341 357 if not hgid:
342 358 eprint("/!\\")
343 359 eprint(r"/!\ Unable to determine hg version from local repository")
344 360 eprint(r"/!\ Failed to retrieve current revision tags")
345 361 return ''
346 362 if numerictags: # tag(s) found
347 363 version = numerictags[-1]
348 364 if hgid.endswith('+'): # propagate the dirty status to the tag
349 365 version += '+'
350 366 else: # no tag found on the checked out revision
351 367 ltagcmd = ['log', '--rev', 'wdir()', '--template', '{latesttag}']
352 368 ltag = sysstr(hg.run(ltagcmd))
353 369 if not ltag:
354 370 eprint("/!\\")
355 371 eprint(r"/!\ Unable to determine hg version from local repository")
356 372 eprint(
357 373 r"/!\ Failed to retrieve current revision distance to lated tag"
358 374 )
359 375 return ''
360 376 changessincecmd = [
361 377 'log',
362 378 '-T',
363 379 'x\n',
364 380 '-r',
365 381 "only(parents(),'%s')" % ltag,
366 382 ]
367 383 changessince = len(hg.run(changessincecmd).splitlines())
368 384 version = '%s+hg%s.%s' % (ltag, changessince, hgid)
369 385 if version.endswith('+'):
370 386 version = version[:-1] + 'local' + time.strftime('%Y%m%d')
371 387 return version
372 388
373 389
374 390 if os.path.isdir('.hg'):
375 391 version = _try_get_version()
376 392 elif os.path.exists('.hg_archival.txt'):
377 393 kw = dict(
378 394 [[t.strip() for t in l.split(':', 1)] for l in open('.hg_archival.txt')]
379 395 )
380 396 if 'tag' in kw:
381 397 version = kw['tag']
382 398 elif 'latesttag' in kw:
383 399 if 'changessincelatesttag' in kw:
384 400 version = (
385 401 '%(latesttag)s+hg%(changessincelatesttag)s.%(node).12s' % kw
386 402 )
387 403 else:
388 404 version = '%(latesttag)s+hg%(latesttagdistance)s.%(node).12s' % kw
389 405 else:
390 406 version = '0+hg' + kw.get('node', '')[:12]
391 407 elif os.path.exists('mercurial/__version__.py'):
392 408 with open('mercurial/__version__.py') as f:
393 409 data = f.read()
394 410 version = re.search('version = b"(.*)"', data).group(1)
395 411 if not version:
396 412 if os.environ.get("MERCURIAL_SETUP_MAKE_LOCAL") == "1":
397 413 version = "0.0+0"
398 414 eprint("/!\\")
399 415 eprint(r"/!\ Using '0.0+0' as the default version")
400 416 eprint(r"/!\ Re-run make local once that first version is built")
401 417 eprint("/!\\")
402 418 else:
403 419 eprint("/!\\")
404 420 eprint(r"/!\ Could not determine the Mercurial version")
405 421 eprint(r"/!\ You need to build a local version first")
406 422 eprint(r"/!\ Run `make local` and try again")
407 423 eprint("/!\\")
408 424 msg = "Run `make local` first to get a working local version"
409 425 raise SystemExit(msg)
410 426
411 427 versionb = version
412 428 if not isinstance(versionb, bytes):
413 429 versionb = versionb.encode('ascii')
414 430
415 431 write_if_changed(
416 432 'mercurial/__version__.py',
417 433 b''.join(
418 434 [
419 435 b'# this file is autogenerated by setup.py\n'
420 436 b'version = b"%s"\n' % versionb,
421 437 ]
422 438 ),
423 439 )
424 440
425 441
426 442 class hgbuild(build):
427 443 # Insert hgbuildmo first so that files in mercurial/locale/ are found
428 444 # when build_py is run next.
429 445 sub_commands = [('build_mo', None)] + build.sub_commands
430 446
431 447
432 448 class hgbuildmo(build):
433 449
434 450 description = "build translations (.mo files)"
435 451
436 452 def run(self):
437 453 if not find_executable('msgfmt'):
438 454 self.warn(
439 455 "could not find msgfmt executable, no translations "
440 456 "will be built"
441 457 )
442 458 return
443 459
444 460 podir = 'i18n'
445 461 if not os.path.isdir(podir):
446 462 self.warn("could not find %s/ directory" % podir)
447 463 return
448 464
449 465 join = os.path.join
450 466 for po in os.listdir(podir):
451 467 if not po.endswith('.po'):
452 468 continue
453 469 pofile = join(podir, po)
454 470 modir = join('locale', po[:-3], 'LC_MESSAGES')
455 471 mofile = join(modir, 'hg.mo')
456 472 mobuildfile = join('mercurial', mofile)
457 473 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
458 474 if sys.platform != 'sunos5':
459 475 # msgfmt on Solaris does not know about -c
460 476 cmd.append('-c')
461 477 self.mkpath(join('mercurial', modir))
462 478 self.make_file([pofile], mobuildfile, spawn, (cmd,))
463 479
464 480
465 481 class hgdist(Distribution):
466 482 pure = False
467 483 rust = False
468 484 no_rust = False
469 485 cffi = ispypy
470 486
471 487 global_options = Distribution.global_options + [
472 488 ('pure', None, "use pure (slow) Python code instead of C extensions"),
473 489 ('rust', None, "use Rust extensions additionally to C extensions"),
474 490 (
475 491 'no-rust',
476 492 None,
477 493 "do not use Rust extensions additionally to C extensions",
478 494 ),
479 495 ]
480 496
481 497 negative_opt = Distribution.negative_opt.copy()
482 498 boolean_options = ['pure', 'rust', 'no-rust']
483 499 negative_opt['no-rust'] = 'rust'
484 500
485 501 def _set_command_options(self, command_obj, option_dict=None):
486 502 # Not all distutils versions in the wild have boolean_options.
487 503 # This should be cleaned up when we're Python 3 only.
488 504 command_obj.boolean_options = (
489 505 getattr(command_obj, 'boolean_options', []) + self.boolean_options
490 506 )
491 507 return Distribution._set_command_options(
492 508 self, command_obj, option_dict=option_dict
493 509 )
494 510
495 511 def parse_command_line(self):
496 512 ret = Distribution.parse_command_line(self)
497 513 if not (self.rust or self.no_rust):
498 514 hgrustext = os.environ.get('HGWITHRUSTEXT')
499 515 # TODO record it for proper rebuild upon changes
500 516 # (see mercurial/__modulepolicy__.py)
501 517 if hgrustext != 'cpython' and hgrustext is not None:
502 518 if hgrustext:
503 519 msg = 'unknown HGWITHRUSTEXT value: %s' % hgrustext
504 520 print(msg, file=sys.stderr)
505 521 hgrustext = None
506 522 self.rust = hgrustext is not None
507 523 self.no_rust = not self.rust
508 524 return ret
509 525
510 526 def has_ext_modules(self):
511 527 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
512 528 # too late for some cases
513 529 return not self.pure and Distribution.has_ext_modules(self)
514 530
515 531
516 532 # This is ugly as a one-liner. So use a variable.
517 533 buildextnegops = dict(getattr(build_ext, 'negative_options', {}))
518 534 buildextnegops['no-zstd'] = 'zstd'
519 535 buildextnegops['no-rust'] = 'rust'
520 536
521 537
522 538 class hgbuildext(build_ext):
523 539 user_options = build_ext.user_options + [
524 540 ('zstd', None, 'compile zstd bindings [default]'),
525 541 ('no-zstd', None, 'do not compile zstd bindings'),
526 542 (
527 543 'rust',
528 544 None,
529 545 'compile Rust extensions if they are in use '
530 546 '(requires Cargo) [default]',
531 547 ),
532 548 ('no-rust', None, 'do not compile Rust extensions'),
533 549 ]
534 550
535 551 boolean_options = build_ext.boolean_options + ['zstd', 'rust']
536 552 negative_opt = buildextnegops
537 553
538 554 def initialize_options(self):
539 555 self.zstd = True
540 556 self.rust = True
541 557
542 558 return build_ext.initialize_options(self)
543 559
544 560 def finalize_options(self):
545 561 # Unless overridden by the end user, build extensions in parallel.
546 562 # Only influences behavior on Python 3.5+.
547 563 if getattr(self, 'parallel', None) is None:
548 564 self.parallel = True
549 565
550 566 return build_ext.finalize_options(self)
551 567
552 568 def build_extensions(self):
553 569 ruststandalones = [
554 570 e for e in self.extensions if isinstance(e, RustStandaloneExtension)
555 571 ]
556 572 self.extensions = [
557 573 e for e in self.extensions if e not in ruststandalones
558 574 ]
559 575 # Filter out zstd if disabled via argument.
560 576 if not self.zstd:
561 577 self.extensions = [
562 578 e for e in self.extensions if e.name != 'mercurial.zstd'
563 579 ]
564 580
565 581 # Build Rust standalone extensions if it'll be used
566 582 # and its build is not explicitly disabled (for external build
567 583 # as Linux distributions would do)
568 584 if self.distribution.rust and self.rust:
569 585 if not sys.platform.startswith('linux'):
570 586 self.warn(
571 587 "rust extensions have only been tested on Linux "
572 588 "and may not behave correctly on other platforms"
573 589 )
574 590
575 591 for rustext in ruststandalones:
576 592 rustext.build('' if self.inplace else self.build_lib)
577 593
578 594 return build_ext.build_extensions(self)
579 595
580 596 def build_extension(self, ext):
581 597 if (
582 598 self.distribution.rust
583 599 and self.rust
584 600 and isinstance(ext, RustExtension)
585 601 ):
586 602 ext.rustbuild()
587 603 try:
588 604 build_ext.build_extension(self, ext)
589 605 except CCompilerError:
590 606 if not getattr(ext, 'optional', False):
591 607 raise
592 608 log.warn(
593 609 "Failed to build optional extension '%s' (skipping)", ext.name
594 610 )
595 611
596 612
597 613 class hgbuildscripts(build_scripts):
598 614 def run(self):
599 615 if os.name != 'nt' or self.distribution.pure:
600 616 return build_scripts.run(self)
601 617
602 618 exebuilt = False
603 619 try:
604 620 self.run_command('build_hgexe')
605 621 exebuilt = True
606 622 except (DistutilsError, CCompilerError):
607 623 log.warn('failed to build optional hg.exe')
608 624
609 625 if exebuilt:
610 626 # Copying hg.exe to the scripts build directory ensures it is
611 627 # installed by the install_scripts command.
612 628 hgexecommand = self.get_finalized_command('build_hgexe')
613 629 dest = os.path.join(self.build_dir, 'hg.exe')
614 630 self.mkpath(self.build_dir)
615 631 self.copy_file(hgexecommand.hgexepath, dest)
616 632
617 633 # Remove hg.bat because it is redundant with hg.exe.
618 634 self.scripts.remove('contrib/win32/hg.bat')
619 635
620 636 return build_scripts.run(self)
621 637
622 638
623 639 class hgbuildpy(build_py):
624 640 def finalize_options(self):
625 641 build_py.finalize_options(self)
626 642
627 643 if self.distribution.pure:
628 644 self.distribution.ext_modules = []
629 645 elif self.distribution.cffi:
630 646 from mercurial.cffi import (
631 647 bdiffbuild,
632 648 mpatchbuild,
633 649 )
634 650
635 651 exts = [
636 652 mpatchbuild.ffi.distutils_extension(),
637 653 bdiffbuild.ffi.distutils_extension(),
638 654 ]
639 655 # cffi modules go here
640 656 if sys.platform == 'darwin':
641 657 from mercurial.cffi import osutilbuild
642 658
643 659 exts.append(osutilbuild.ffi.distutils_extension())
644 660 self.distribution.ext_modules = exts
645 661 else:
646 662 h = os.path.join(get_python_inc(), 'Python.h')
647 663 if not os.path.exists(h):
648 664 raise SystemExit(
649 665 'Python headers are required to build '
650 666 'Mercurial but weren\'t found in %s' % h
651 667 )
652 668
653 669 def run(self):
654 670 basepath = os.path.join(self.build_lib, 'mercurial')
655 671 self.mkpath(basepath)
656 672
657 673 rust = self.distribution.rust
658 674 if self.distribution.pure:
659 675 modulepolicy = 'py'
660 676 elif self.build_lib == '.':
661 677 # in-place build should run without rebuilding and Rust extensions
662 678 modulepolicy = 'rust+c-allow' if rust else 'allow'
663 679 else:
664 680 modulepolicy = 'rust+c' if rust else 'c'
665 681
666 682 content = b''.join(
667 683 [
668 684 b'# this file is autogenerated by setup.py\n',
669 685 b'modulepolicy = b"%s"\n' % modulepolicy.encode('ascii'),
670 686 ]
671 687 )
672 688 write_if_changed(os.path.join(basepath, '__modulepolicy__.py'), content)
673 689
674 690 build_py.run(self)
675 691
676 692
677 693 class buildhgextindex(Command):
678 694 description = 'generate prebuilt index of hgext (for frozen package)'
679 695 user_options = []
680 696 _indexfilename = 'hgext/__index__.py'
681 697
682 698 def initialize_options(self):
683 699 pass
684 700
685 701 def finalize_options(self):
686 702 pass
687 703
688 704 def run(self):
689 705 if os.path.exists(self._indexfilename):
690 706 with open(self._indexfilename, 'w') as f:
691 707 f.write('# empty\n')
692 708
693 709 # here no extension enabled, disabled() lists up everything
694 710 code = (
695 711 'import pprint; from mercurial import extensions; '
696 712 'ext = extensions.disabled();'
697 713 'ext.pop("__index__", None);'
698 714 'pprint.pprint(ext)'
699 715 )
700 716 returncode, out, err = runcmd(
701 717 [sys.executable, '-c', code], localhgenv()
702 718 )
703 719 if err or returncode != 0:
704 720 raise DistutilsExecError(err)
705 721
706 722 with open(self._indexfilename, 'wb') as f:
707 723 f.write(b'# this file is autogenerated by setup.py\n')
708 724 f.write(b'docs = ')
709 725 f.write(out)
710 726
711 727
712 728 class buildhgexe(build_ext):
713 729 description = 'compile hg.exe from mercurial/exewrapper.c'
714 730
715 731 LONG_PATHS_MANIFEST = """\
716 732 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
717 733 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
718 734 <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
719 735 <security>
720 736 <requestedPrivileges>
721 737 <requestedExecutionLevel
722 738 level="asInvoker"
723 739 uiAccess="false"
724 740 />
725 741 </requestedPrivileges>
726 742 </security>
727 743 </trustInfo>
728 744 <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
729 745 <application>
730 746 <!-- Windows Vista -->
731 747 <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
732 748 <!-- Windows 7 -->
733 749 <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
734 750 <!-- Windows 8 -->
735 751 <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
736 752 <!-- Windows 8.1 -->
737 753 <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
738 754 <!-- Windows 10 and Windows 11 -->
739 755 <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
740 756 </application>
741 757 </compatibility>
742 758 <application xmlns="urn:schemas-microsoft-com:asm.v3">
743 759 <windowsSettings
744 760 xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
745 761 <ws2:longPathAware>true</ws2:longPathAware>
746 762 </windowsSettings>
747 763 </application>
748 764 <dependency>
749 765 <dependentAssembly>
750 766 <assemblyIdentity type="win32"
751 767 name="Microsoft.Windows.Common-Controls"
752 768 version="6.0.0.0"
753 769 processorArchitecture="*"
754 770 publicKeyToken="6595b64144ccf1df"
755 771 language="*" />
756 772 </dependentAssembly>
757 773 </dependency>
758 774 </assembly>
759 775 """
760 776
761 777 def initialize_options(self):
762 778 build_ext.initialize_options(self)
763 779
764 780 def build_extensions(self):
765 781 if os.name != 'nt':
766 782 return
767 783 if isinstance(self.compiler, HackedMingw32CCompiler):
768 784 self.compiler.compiler_so = self.compiler.compiler # no -mdll
769 785 self.compiler.dll_libraries = [] # no -lmsrvc90
770 786
771 787 pythonlib = None
772 788
773 789 dirname = os.path.dirname(self.get_ext_fullpath('dummy'))
774 790 self.hgtarget = os.path.join(dirname, 'hg')
775 791
776 792 if getattr(sys, 'dllhandle', None):
777 793 # Different Python installs can have different Python library
778 794 # names. e.g. the official CPython distribution uses pythonXY.dll
779 795 # and MinGW uses libpythonX.Y.dll.
780 796 _kernel32 = ctypes.windll.kernel32
781 797 _kernel32.GetModuleFileNameA.argtypes = [
782 798 ctypes.c_void_p,
783 799 ctypes.c_void_p,
784 800 ctypes.c_ulong,
785 801 ]
786 802 _kernel32.GetModuleFileNameA.restype = ctypes.c_ulong
787 803 size = 1000
788 804 buf = ctypes.create_string_buffer(size + 1)
789 805 filelen = _kernel32.GetModuleFileNameA(
790 806 sys.dllhandle, ctypes.byref(buf), size
791 807 )
792 808
793 809 if filelen > 0 and filelen != size:
794 810 dllbasename = os.path.basename(buf.value)
795 811 if not dllbasename.lower().endswith(b'.dll'):
796 812 raise SystemExit(
797 813 'Python DLL does not end with .dll: %s' % dllbasename
798 814 )
799 815 pythonlib = dllbasename[:-4]
800 816
801 817 # Copy the pythonXY.dll next to the binary so that it runs
802 818 # without tampering with PATH.
803 819 dest = os.path.join(
804 820 os.path.dirname(self.hgtarget),
805 821 os.fsdecode(dllbasename),
806 822 )
807 823
808 824 if not os.path.exists(dest):
809 825 shutil.copy(buf.value, dest)
810 826
811 827 # Also overwrite python3.dll so that hgext.git is usable.
812 828 # TODO: also handle the MSYS flavor
813 829 python_x = os.path.join(
814 830 os.path.dirname(os.fsdecode(buf.value)),
815 831 "python3.dll",
816 832 )
817 833
818 834 if os.path.exists(python_x):
819 835 dest = os.path.join(
820 836 os.path.dirname(self.hgtarget),
821 837 os.path.basename(python_x),
822 838 )
823 839
824 840 shutil.copy(python_x, dest)
825 841
826 842 if not pythonlib:
827 843 log.warn(
828 844 'could not determine Python DLL filename; assuming pythonXY'
829 845 )
830 846
831 847 hv = sys.hexversion
832 848 pythonlib = b'python%d%d' % (hv >> 24, (hv >> 16) & 0xFF)
833 849
834 850 log.info('using %s as Python library name' % pythonlib)
835 851 with open('mercurial/hgpythonlib.h', 'wb') as f:
836 852 f.write(b'/* this file is autogenerated by setup.py */\n')
837 853 f.write(b'#define HGPYTHONLIB "%s"\n' % pythonlib)
838 854
839 855 objects = self.compiler.compile(
840 856 ['mercurial/exewrapper.c'],
841 857 output_dir=self.build_temp,
842 858 macros=[('_UNICODE', None), ('UNICODE', None)],
843 859 )
844 860 self.compiler.link_executable(
845 861 objects, self.hgtarget, libraries=[], output_dir=self.build_temp
846 862 )
847 863
848 864 self.addlongpathsmanifest()
849 865
850 866 def addlongpathsmanifest(self):
851 867 """Add manifest pieces so that hg.exe understands long paths
852 868
853 869 Why resource #1 should be used for .exe manifests? I don't know and
854 870 wasn't able to find an explanation for mortals. But it seems to work.
855 871 """
856 872 exefname = self.compiler.executable_filename(self.hgtarget)
857 873 fdauto, manfname = tempfile.mkstemp(suffix='.hg.exe.manifest')
858 874 os.close(fdauto)
859 875 with open(manfname, 'w', encoding="UTF-8") as f:
860 876 f.write(self.LONG_PATHS_MANIFEST)
861 877 log.info("long paths manifest is written to '%s'" % manfname)
862 878 outputresource = '-outputresource:%s;#1' % exefname
863 879 log.info("running mt.exe to update hg.exe's manifest in-place")
864 880
865 881 self.spawn(
866 882 [
867 883 self.compiler.mt,
868 884 '-nologo',
869 885 '-manifest',
870 886 manfname,
871 887 outputresource,
872 888 ]
873 889 )
874 890 log.info("done updating hg.exe's manifest")
875 891 os.remove(manfname)
876 892
877 893 @property
878 894 def hgexepath(self):
879 895 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
880 896 return os.path.join(self.build_temp, dir, 'hg.exe')
881 897
882 898
883 899 class hgbuilddoc(Command):
884 900 description = 'build documentation'
885 901 user_options = [
886 902 ('man', None, 'generate man pages'),
887 903 ('html', None, 'generate html pages'),
888 904 ]
889 905
890 906 def initialize_options(self):
891 907 self.man = None
892 908 self.html = None
893 909
894 910 def finalize_options(self):
895 911 # If --man or --html are set, only generate what we're told to.
896 912 # Otherwise generate everything.
897 913 have_subset = self.man is not None or self.html is not None
898 914
899 915 if have_subset:
900 916 self.man = True if self.man else False
901 917 self.html = True if self.html else False
902 918 else:
903 919 self.man = True
904 920 self.html = True
905 921
906 922 def run(self):
907 923 def normalizecrlf(p):
908 924 with open(p, 'rb') as fh:
909 925 orig = fh.read()
910 926
911 927 if b'\r\n' not in orig:
912 928 return
913 929
914 930 log.info('normalizing %s to LF line endings' % p)
915 931 with open(p, 'wb') as fh:
916 932 fh.write(orig.replace(b'\r\n', b'\n'))
917 933
918 934 def gentxt(root):
919 935 txt = 'doc/%s.txt' % root
920 936 log.info('generating %s' % txt)
921 937 res, out, err = runcmd(
922 938 [sys.executable, 'gendoc.py', root], os.environ, cwd='doc'
923 939 )
924 940 if res:
925 941 raise SystemExit(
926 942 'error running gendoc.py: %s'
927 943 % '\n'.join([sysstr(out), sysstr(err)])
928 944 )
929 945
930 946 with open(txt, 'wb') as fh:
931 947 fh.write(out)
932 948
933 949 def gengendoc(root):
934 950 gendoc = 'doc/%s.gendoc.txt' % root
935 951
936 952 log.info('generating %s' % gendoc)
937 953 res, out, err = runcmd(
938 954 [sys.executable, 'gendoc.py', '%s.gendoc' % root],
939 955 os.environ,
940 956 cwd='doc',
941 957 )
942 958 if res:
943 959 raise SystemExit(
944 960 'error running gendoc: %s'
945 961 % '\n'.join([sysstr(out), sysstr(err)])
946 962 )
947 963
948 964 with open(gendoc, 'wb') as fh:
949 965 fh.write(out)
950 966
951 967 def genman(root):
952 968 log.info('generating doc/%s' % root)
953 969 res, out, err = runcmd(
954 970 [
955 971 sys.executable,
956 972 'runrst',
957 973 'hgmanpage',
958 974 '--halt',
959 975 'warning',
960 976 '--strip-elements-with-class',
961 977 'htmlonly',
962 978 '%s.txt' % root,
963 979 root,
964 980 ],
965 981 os.environ,
966 982 cwd='doc',
967 983 )
968 984 if res:
969 985 raise SystemExit(
970 986 'error running runrst: %s'
971 987 % '\n'.join([sysstr(out), sysstr(err)])
972 988 )
973 989
974 990 normalizecrlf('doc/%s' % root)
975 991
976 992 def genhtml(root):
977 993 log.info('generating doc/%s.html' % root)
978 994 res, out, err = runcmd(
979 995 [
980 996 sys.executable,
981 997 'runrst',
982 998 'html',
983 999 '--halt',
984 1000 'warning',
985 1001 '--link-stylesheet',
986 1002 '--stylesheet-path',
987 1003 'style.css',
988 1004 '%s.txt' % root,
989 1005 '%s.html' % root,
990 1006 ],
991 1007 os.environ,
992 1008 cwd='doc',
993 1009 )
994 1010 if res:
995 1011 raise SystemExit(
996 1012 'error running runrst: %s'
997 1013 % '\n'.join([sysstr(out), sysstr(err)])
998 1014 )
999 1015
1000 1016 normalizecrlf('doc/%s.html' % root)
1001 1017
1002 1018 # This logic is duplicated in doc/Makefile.
1003 1019 sources = {
1004 1020 f
1005 1021 for f in os.listdir('mercurial/helptext')
1006 1022 if re.search(r'[0-9]\.txt$', f)
1007 1023 }
1008 1024
1009 1025 # common.txt is a one-off.
1010 1026 gentxt('common')
1011 1027
1012 1028 for source in sorted(sources):
1013 1029 assert source[-4:] == '.txt'
1014 1030 root = source[:-4]
1015 1031
1016 1032 gentxt(root)
1017 1033 gengendoc(root)
1018 1034
1019 1035 if self.man:
1020 1036 genman(root)
1021 1037 if self.html:
1022 1038 genhtml(root)
1023 1039
1024 1040
1025 1041 class hginstall(install):
1026 1042
1027 1043 user_options = install.user_options + [
1028 1044 (
1029 1045 'old-and-unmanageable',
1030 1046 None,
1031 1047 'noop, present for eggless setuptools compat',
1032 1048 ),
1033 1049 (
1034 1050 'single-version-externally-managed',
1035 1051 None,
1036 1052 'noop, present for eggless setuptools compat',
1037 1053 ),
1038 1054 ]
1039 1055
1040 1056 sub_commands = install.sub_commands + [
1041 1057 ('install_completion', lambda self: True)
1042 1058 ]
1043 1059
1044 1060 # Also helps setuptools not be sad while we refuse to create eggs.
1045 1061 single_version_externally_managed = True
1046 1062
1047 1063 def get_sub_commands(self):
1048 1064 # Screen out egg related commands to prevent egg generation. But allow
1049 1065 # mercurial.egg-info generation, since that is part of modern
1050 1066 # packaging.
1051 1067 excl = {'bdist_egg'}
1052 1068 return filter(lambda x: x not in excl, install.get_sub_commands(self))
1053 1069
1054 1070
1055 1071 class hginstalllib(install_lib):
1056 1072 """
1057 1073 This is a specialization of install_lib that replaces the copy_file used
1058 1074 there so that it supports setting the mode of files after copying them,
1059 1075 instead of just preserving the mode that the files originally had. If your
1060 1076 system has a umask of something like 027, preserving the permissions when
1061 1077 copying will lead to a broken install.
1062 1078
1063 1079 Note that just passing keep_permissions=False to copy_file would be
1064 1080 insufficient, as it might still be applying a umask.
1065 1081 """
1066 1082
1067 1083 def run(self):
1068 1084 realcopyfile = file_util.copy_file
1069 1085
1070 1086 def copyfileandsetmode(*args, **kwargs):
1071 1087 src, dst = args[0], args[1]
1072 1088 dst, copied = realcopyfile(*args, **kwargs)
1073 1089 if copied:
1074 1090 st = os.stat(src)
1075 1091 # Persist executable bit (apply it to group and other if user
1076 1092 # has it)
1077 1093 if st[stat.ST_MODE] & stat.S_IXUSR:
1078 1094 setmode = int('0755', 8)
1079 1095 else:
1080 1096 setmode = int('0644', 8)
1081 1097 m = stat.S_IMODE(st[stat.ST_MODE])
1082 1098 m = (m & ~int('0777', 8)) | setmode
1083 1099 os.chmod(dst, m)
1084 1100
1085 1101 file_util.copy_file = copyfileandsetmode
1086 1102 try:
1087 1103 install_lib.run(self)
1088 1104 finally:
1089 1105 file_util.copy_file = realcopyfile
1090 1106
1091 1107
1092 1108 class hginstallscripts(install_scripts):
1093 1109 """
1094 1110 This is a specialization of install_scripts that replaces the @LIBDIR@ with
1095 1111 the configured directory for modules. If possible, the path is made relative
1096 1112 to the directory for scripts.
1097 1113 """
1098 1114
1099 1115 def initialize_options(self):
1100 1116 install_scripts.initialize_options(self)
1101 1117
1102 1118 self.install_lib = None
1103 1119
1104 1120 def finalize_options(self):
1105 1121 install_scripts.finalize_options(self)
1106 1122 self.set_undefined_options('install', ('install_lib', 'install_lib'))
1107 1123
1108 1124 def run(self):
1109 1125 install_scripts.run(self)
1110 1126
1111 1127 # It only makes sense to replace @LIBDIR@ with the install path if
1112 1128 # the install path is known. For wheels, the logic below calculates
1113 1129 # the libdir to be "../..". This is because the internal layout of a
1114 1130 # wheel archive looks like:
1115 1131 #
1116 1132 # mercurial-3.6.1.data/scripts/hg
1117 1133 # mercurial/__init__.py
1118 1134 #
1119 1135 # When installing wheels, the subdirectories of the "<pkg>.data"
1120 1136 # directory are translated to system local paths and files therein
1121 1137 # are copied in place. The mercurial/* files are installed into the
1122 1138 # site-packages directory. However, the site-packages directory
1123 1139 # isn't known until wheel install time. This means we have no clue
1124 1140 # at wheel generation time what the installed site-packages directory
1125 1141 # will be. And, wheels don't appear to provide the ability to register
1126 1142 # custom code to run during wheel installation. This all means that
1127 1143 # we can't reliably set the libdir in wheels: the default behavior
1128 1144 # of looking in sys.path must do.
1129 1145
1130 1146 if (
1131 1147 os.path.splitdrive(self.install_dir)[0]
1132 1148 != os.path.splitdrive(self.install_lib)[0]
1133 1149 ):
1134 1150 # can't make relative paths from one drive to another, so use an
1135 1151 # absolute path instead
1136 1152 libdir = self.install_lib
1137 1153 else:
1138 1154 libdir = os.path.relpath(self.install_lib, self.install_dir)
1139 1155
1140 1156 for outfile in self.outfiles:
1141 1157 with open(outfile, 'rb') as fp:
1142 1158 data = fp.read()
1143 1159
1144 1160 # skip binary files
1145 1161 if b'\0' in data:
1146 1162 continue
1147 1163
1148 1164 # During local installs, the shebang will be rewritten to the final
1149 1165 # install path. During wheel packaging, the shebang has a special
1150 1166 # value.
1151 1167 if data.startswith(b'#!python'):
1152 1168 log.info(
1153 1169 'not rewriting @LIBDIR@ in %s because install path '
1154 1170 'not known' % outfile
1155 1171 )
1156 1172 continue
1157 1173
1158 1174 data = data.replace(b'@LIBDIR@', libdir.encode('unicode_escape'))
1159 1175 with open(outfile, 'wb') as fp:
1160 1176 fp.write(data)
1161 1177
1162 1178
1163 1179 class hginstallcompletion(Command):
1164 1180 description = 'Install shell completion'
1165 1181
1166 1182 def initialize_options(self):
1167 1183 self.install_dir = None
1168 1184 self.outputs = []
1169 1185
1170 1186 def finalize_options(self):
1171 1187 self.set_undefined_options(
1172 1188 'install_data', ('install_dir', 'install_dir')
1173 1189 )
1174 1190
1175 1191 def get_outputs(self):
1176 1192 return self.outputs
1177 1193
1178 1194 def run(self):
1179 1195 for src, dir_path, dest in (
1180 1196 (
1181 1197 'bash_completion',
1182 1198 ('share', 'bash-completion', 'completions'),
1183 1199 'hg',
1184 1200 ),
1185 1201 ('zsh_completion', ('share', 'zsh', 'site-functions'), '_hg'),
1186 1202 ):
1187 1203 dir = os.path.join(self.install_dir, *dir_path)
1188 1204 self.mkpath(dir)
1189 1205
1190 1206 dest = os.path.join(dir, dest)
1191 1207 self.outputs.append(dest)
1192 1208 self.copy_file(os.path.join('contrib', src), dest)
1193 1209
1194 1210
1195 1211 # virtualenv installs custom distutils/__init__.py and
1196 1212 # distutils/distutils.cfg files which essentially proxy back to the
1197 1213 # "real" distutils in the main Python install. The presence of this
1198 1214 # directory causes py2exe to pick up the "hacked" distutils package
1199 1215 # from the virtualenv and "import distutils" will fail from the py2exe
1200 1216 # build because the "real" distutils files can't be located.
1201 1217 #
1202 1218 # We work around this by monkeypatching the py2exe code finding Python
1203 1219 # modules to replace the found virtualenv distutils modules with the
1204 1220 # original versions via filesystem scanning. This is a bit hacky. But
1205 1221 # it allows us to use virtualenvs for py2exe packaging, which is more
1206 1222 # deterministic and reproducible.
1207 1223 #
1208 1224 # It's worth noting that the common StackOverflow suggestions for this
1209 1225 # problem involve copying the original distutils files into the
1210 1226 # virtualenv or into the staging directory after setup() is invoked.
1211 1227 # The former is very brittle and can easily break setup(). Our hacking
1212 1228 # of the found modules routine has a similar result as copying the files
1213 1229 # manually. But it makes fewer assumptions about how py2exe works and
1214 1230 # is less brittle.
1215 1231
1216 1232 # This only catches virtualenvs made with virtualenv (as opposed to
1217 1233 # venv, which is likely what Python 3 uses).
1218 1234 py2exehacked = py2exeloaded and getattr(sys, 'real_prefix', None) is not None
1219 1235
1220 1236 if py2exehacked:
1221 1237 from distutils.command.py2exe import py2exe as buildpy2exe
1222 1238 from py2exe.mf import Module as py2exemodule
1223 1239
1224 1240 class hgbuildpy2exe(buildpy2exe):
1225 1241 def find_needed_modules(self, mf, files, modules):
1226 1242 res = buildpy2exe.find_needed_modules(self, mf, files, modules)
1227 1243
1228 1244 # Replace virtualenv's distutils modules with the real ones.
1229 1245 modules = {}
1230 1246 for k, v in res.modules.items():
1231 1247 if k != 'distutils' and not k.startswith('distutils.'):
1232 1248 modules[k] = v
1233 1249
1234 1250 res.modules = modules
1235 1251
1236 1252 import opcode
1237 1253
1238 1254 distutilsreal = os.path.join(
1239 1255 os.path.dirname(opcode.__file__), 'distutils'
1240 1256 )
1241 1257
1242 1258 for root, dirs, files in os.walk(distutilsreal):
1243 1259 for f in sorted(files):
1244 1260 if not f.endswith('.py'):
1245 1261 continue
1246 1262
1247 1263 full = os.path.join(root, f)
1248 1264
1249 1265 parents = ['distutils']
1250 1266
1251 1267 if root != distutilsreal:
1252 1268 rel = os.path.relpath(root, distutilsreal)
1253 1269 parents.extend(p for p in rel.split(os.sep))
1254 1270
1255 1271 modname = '%s.%s' % ('.'.join(parents), f[:-3])
1256 1272
1257 1273 if modname.startswith('distutils.tests.'):
1258 1274 continue
1259 1275
1260 1276 if modname.endswith('.__init__'):
1261 1277 modname = modname[: -len('.__init__')]
1262 1278 path = os.path.dirname(full)
1263 1279 else:
1264 1280 path = None
1265 1281
1266 1282 res.modules[modname] = py2exemodule(
1267 1283 modname, full, path=path
1268 1284 )
1269 1285
1270 1286 if 'distutils' not in res.modules:
1271 1287 raise SystemExit('could not find distutils modules')
1272 1288
1273 1289 return res
1274 1290
1275 1291
1276 1292 cmdclass = {
1277 1293 'build': hgbuild,
1278 1294 'build_doc': hgbuilddoc,
1279 1295 'build_mo': hgbuildmo,
1280 1296 'build_ext': hgbuildext,
1281 1297 'build_py': hgbuildpy,
1282 1298 'build_scripts': hgbuildscripts,
1283 1299 'build_hgextindex': buildhgextindex,
1284 1300 'install': hginstall,
1285 1301 'install_completion': hginstallcompletion,
1286 1302 'install_lib': hginstalllib,
1287 1303 'install_scripts': hginstallscripts,
1288 1304 'build_hgexe': buildhgexe,
1289 1305 }
1290 1306
1291 1307 if py2exehacked:
1292 1308 cmdclass['py2exe'] = hgbuildpy2exe
1293 1309
1294 1310 packages = [
1295 1311 'mercurial',
1296 1312 'mercurial.cext',
1297 1313 'mercurial.cffi',
1298 1314 'mercurial.defaultrc',
1299 1315 'mercurial.dirstateutils',
1300 1316 'mercurial.helptext',
1301 1317 'mercurial.helptext.internals',
1302 1318 'mercurial.hgweb',
1303 1319 'mercurial.interfaces',
1304 1320 'mercurial.pure',
1305 1321 'mercurial.stabletailgraph',
1306 1322 'mercurial.templates',
1307 1323 'mercurial.thirdparty',
1308 1324 'mercurial.thirdparty.attr',
1309 1325 'mercurial.thirdparty.tomli',
1310 1326 'mercurial.thirdparty.zope',
1311 1327 'mercurial.thirdparty.zope.interface',
1312 1328 'mercurial.upgrade_utils',
1313 1329 'mercurial.utils',
1314 1330 'mercurial.revlogutils',
1315 1331 'mercurial.testing',
1316 1332 'hgext',
1317 1333 'hgext.convert',
1318 1334 'hgext.fsmonitor',
1319 1335 'hgext.fastannotate',
1320 1336 'hgext.fsmonitor.pywatchman',
1321 1337 'hgext.git',
1322 1338 'hgext.highlight',
1323 1339 'hgext.hooklib',
1324 1340 'hgext.largefiles',
1325 1341 'hgext.lfs',
1326 1342 'hgext.narrow',
1327 1343 'hgext.remotefilelog',
1328 1344 'hgext.zeroconf',
1329 1345 'hgext3rd',
1330 1346 'hgdemandimport',
1331 1347 ]
1332 1348
1333 1349 for name in os.listdir(os.path.join('mercurial', 'templates')):
1334 1350 if name != '__pycache__' and os.path.isdir(
1335 1351 os.path.join('mercurial', 'templates', name)
1336 1352 ):
1337 1353 packages.append('mercurial.templates.%s' % name)
1338 1354
1339 1355 if 'HG_PY2EXE_EXTRA_INSTALL_PACKAGES' in os.environ:
1340 1356 # py2exe can't cope with namespace packages very well, so we have to
1341 1357 # install any hgext3rd.* extensions that we want in the final py2exe
1342 1358 # image here. This is gross, but you gotta do what you gotta do.
1343 1359 packages.extend(os.environ['HG_PY2EXE_EXTRA_INSTALL_PACKAGES'].split(' '))
1344 1360
1345 1361 common_depends = [
1346 1362 'mercurial/bitmanipulation.h',
1347 1363 'mercurial/compat.h',
1348 1364 'mercurial/cext/util.h',
1349 1365 ]
1350 1366 common_include_dirs = ['mercurial']
1351 1367
1352 1368 common_cflags = []
1353 1369
1354 1370 # MSVC 2008 still needs declarations at the top of the scope, but Python 3.9
1355 1371 # makes declarations not at the top of a scope in the headers.
1356 1372 if os.name != 'nt' and sys.version_info[1] < 9:
1357 1373 common_cflags = ['-Werror=declaration-after-statement']
1358 1374
1359 1375 osutil_cflags = []
1360 1376 osutil_ldflags = []
1361 1377
1362 1378 # platform specific macros
1363 1379 for plat, func in [('bsd', 'setproctitle')]:
1364 1380 if re.search(plat, sys.platform) and hasfunction(new_compiler(), func):
1365 1381 osutil_cflags.append('-DHAVE_%s' % func.upper())
1366 1382
1367 1383 for plat, macro, code in [
1368 1384 (
1369 1385 'bsd|darwin',
1370 1386 'BSD_STATFS',
1371 1387 '''
1372 1388 #include <sys/param.h>
1373 1389 #include <sys/mount.h>
1374 1390 int main() { struct statfs s; return sizeof(s.f_fstypename); }
1375 1391 ''',
1376 1392 ),
1377 1393 (
1378 1394 'linux',
1379 1395 'LINUX_STATFS',
1380 1396 '''
1381 1397 #include <linux/magic.h>
1382 1398 #include <sys/vfs.h>
1383 1399 int main() { struct statfs s; return sizeof(s.f_type); }
1384 1400 ''',
1385 1401 ),
1386 1402 ]:
1387 1403 if re.search(plat, sys.platform) and cancompile(new_compiler(), code):
1388 1404 osutil_cflags.append('-DHAVE_%s' % macro)
1389 1405
1390 1406 if sys.platform == 'darwin':
1391 1407 osutil_ldflags += ['-framework', 'ApplicationServices']
1392 1408
1393 1409 if sys.platform == 'sunos5':
1394 1410 osutil_ldflags += ['-lsocket']
1395 1411
1396 1412 xdiff_srcs = [
1397 1413 'mercurial/thirdparty/xdiff/xdiffi.c',
1398 1414 'mercurial/thirdparty/xdiff/xprepare.c',
1399 1415 'mercurial/thirdparty/xdiff/xutils.c',
1400 1416 ]
1401 1417
1402 1418 xdiff_headers = [
1403 1419 'mercurial/thirdparty/xdiff/xdiff.h',
1404 1420 'mercurial/thirdparty/xdiff/xdiffi.h',
1405 1421 'mercurial/thirdparty/xdiff/xinclude.h',
1406 1422 'mercurial/thirdparty/xdiff/xmacros.h',
1407 1423 'mercurial/thirdparty/xdiff/xprepare.h',
1408 1424 'mercurial/thirdparty/xdiff/xtypes.h',
1409 1425 'mercurial/thirdparty/xdiff/xutils.h',
1410 1426 ]
1411 1427
1412 1428
1413 1429 class RustCompilationError(CCompilerError):
1414 1430 """Exception class for Rust compilation errors."""
1415 1431
1416 1432
1417 1433 class RustExtension(Extension):
1418 1434 """Base classes for concrete Rust Extension classes."""
1419 1435
1420 1436 rusttargetdir = os.path.join('rust', 'target', 'release')
1421 1437
1422 1438 def __init__(self, mpath, sources, rustlibname, subcrate, **kw):
1423 1439 Extension.__init__(self, mpath, sources, **kw)
1424 1440 srcdir = self.rustsrcdir = os.path.join('rust', subcrate)
1425 1441
1426 1442 # adding Rust source and control files to depends so that the extension
1427 1443 # gets rebuilt if they've changed
1428 1444 self.depends.append(os.path.join(srcdir, 'Cargo.toml'))
1429 1445 cargo_lock = os.path.join(srcdir, 'Cargo.lock')
1430 1446 if os.path.exists(cargo_lock):
1431 1447 self.depends.append(cargo_lock)
1432 1448 for dirpath, subdir, fnames in os.walk(os.path.join(srcdir, 'src')):
1433 1449 self.depends.extend(
1434 1450 os.path.join(dirpath, fname)
1435 1451 for fname in fnames
1436 1452 if os.path.splitext(fname)[1] == '.rs'
1437 1453 )
1438 1454
1439 1455 @staticmethod
1440 1456 def rustdylibsuffix():
1441 1457 """Return the suffix for shared libraries produced by rustc.
1442 1458
1443 1459 See also: https://doc.rust-lang.org/reference/linkage.html
1444 1460 """
1445 1461 if sys.platform == 'darwin':
1446 1462 return '.dylib'
1447 1463 elif os.name == 'nt':
1448 1464 return '.dll'
1449 1465 else:
1450 1466 return '.so'
1451 1467
1452 1468 def rustbuild(self):
1453 1469 env = os.environ.copy()
1454 1470 if 'HGTEST_RESTOREENV' in env:
1455 1471 # Mercurial tests change HOME to a temporary directory,
1456 1472 # but, if installed with rustup, the Rust toolchain needs
1457 1473 # HOME to be correct (otherwise the 'no default toolchain'
1458 1474 # error message is issued and the build fails).
1459 1475 # This happens currently with test-hghave.t, which does
1460 1476 # invoke this build.
1461 1477
1462 1478 # Unix only fix (os.path.expanduser not really reliable if
1463 1479 # HOME is shadowed like this)
1464 1480 import pwd
1465 1481
1466 1482 env['HOME'] = pwd.getpwuid(os.getuid()).pw_dir
1467 1483
1468 1484 cargocmd = ['cargo', 'rustc', '--release']
1469 1485
1470 1486 rust_features = env.get("HG_RUST_FEATURES")
1471 1487 if rust_features:
1472 1488 cargocmd.extend(('--features', rust_features))
1473 1489
1474 1490 cargocmd.append('--')
1475 1491 if sys.platform == 'darwin':
1476 1492 cargocmd.extend(
1477 1493 ("-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup")
1478 1494 )
1479 1495 try:
1480 1496 subprocess.check_call(cargocmd, env=env, cwd=self.rustsrcdir)
1481 1497 except FileNotFoundError:
1482 1498 raise RustCompilationError("Cargo not found")
1483 1499 except PermissionError:
1484 1500 raise RustCompilationError(
1485 1501 "Cargo found, but permission to execute it is denied"
1486 1502 )
1487 1503 except subprocess.CalledProcessError:
1488 1504 raise RustCompilationError(
1489 1505 "Cargo failed. Working directory: %r, "
1490 1506 "command: %r, environment: %r"
1491 1507 % (self.rustsrcdir, cargocmd, env)
1492 1508 )
1493 1509
1494 1510
1495 1511 class RustStandaloneExtension(RustExtension):
1496 1512 def __init__(self, pydottedname, rustcrate, dylibname, **kw):
1497 1513 RustExtension.__init__(
1498 1514 self, pydottedname, [], dylibname, rustcrate, **kw
1499 1515 )
1500 1516 self.dylibname = dylibname
1501 1517
1502 1518 def build(self, target_dir):
1503 1519 self.rustbuild()
1504 1520 target = [target_dir]
1505 1521 target.extend(self.name.split('.'))
1506 1522 target[-1] += DYLIB_SUFFIX
1507 1523 target = os.path.join(*target)
1508 1524 os.makedirs(os.path.dirname(target), exist_ok=True)
1509 1525 shutil.copy2(
1510 1526 os.path.join(
1511 1527 self.rusttargetdir, self.dylibname + self.rustdylibsuffix()
1512 1528 ),
1513 1529 target,
1514 1530 )
1515 1531
1516 1532
1517 1533 extmodules = [
1518 1534 Extension(
1519 1535 'mercurial.cext.base85',
1520 1536 ['mercurial/cext/base85.c'],
1521 1537 include_dirs=common_include_dirs,
1522 1538 extra_compile_args=common_cflags,
1523 1539 depends=common_depends,
1524 1540 ),
1525 1541 Extension(
1526 1542 'mercurial.cext.bdiff',
1527 1543 ['mercurial/bdiff.c', 'mercurial/cext/bdiff.c'] + xdiff_srcs,
1528 1544 include_dirs=common_include_dirs,
1529 1545 extra_compile_args=common_cflags,
1530 1546 depends=common_depends + ['mercurial/bdiff.h'] + xdiff_headers,
1531 1547 ),
1532 1548 Extension(
1533 1549 'mercurial.cext.mpatch',
1534 1550 ['mercurial/mpatch.c', 'mercurial/cext/mpatch.c'],
1535 1551 include_dirs=common_include_dirs,
1536 1552 extra_compile_args=common_cflags,
1537 1553 depends=common_depends,
1538 1554 ),
1539 1555 Extension(
1540 1556 'mercurial.cext.parsers',
1541 1557 [
1542 1558 'mercurial/cext/charencode.c',
1543 1559 'mercurial/cext/dirs.c',
1544 1560 'mercurial/cext/manifest.c',
1545 1561 'mercurial/cext/parsers.c',
1546 1562 'mercurial/cext/pathencode.c',
1547 1563 'mercurial/cext/revlog.c',
1548 1564 ],
1549 1565 include_dirs=common_include_dirs,
1550 1566 extra_compile_args=common_cflags,
1551 1567 depends=common_depends
1552 1568 + [
1553 1569 'mercurial/cext/charencode.h',
1554 1570 'mercurial/cext/revlog.h',
1555 1571 ],
1556 1572 ),
1557 1573 Extension(
1558 1574 'mercurial.cext.osutil',
1559 1575 ['mercurial/cext/osutil.c'],
1560 1576 include_dirs=common_include_dirs,
1561 1577 extra_compile_args=common_cflags + osutil_cflags,
1562 1578 extra_link_args=osutil_ldflags,
1563 1579 depends=common_depends,
1564 1580 ),
1565 1581 Extension(
1566 1582 'mercurial.thirdparty.zope.interface._zope_interface_coptimizations',
1567 1583 [
1568 1584 'mercurial/thirdparty/zope/interface/_zope_interface_coptimizations.c',
1569 1585 ],
1570 1586 extra_compile_args=common_cflags,
1571 1587 ),
1572 1588 Extension(
1573 1589 'mercurial.thirdparty.sha1dc',
1574 1590 [
1575 1591 'mercurial/thirdparty/sha1dc/cext.c',
1576 1592 'mercurial/thirdparty/sha1dc/lib/sha1.c',
1577 1593 'mercurial/thirdparty/sha1dc/lib/ubc_check.c',
1578 1594 ],
1579 1595 extra_compile_args=common_cflags,
1580 1596 ),
1581 1597 Extension(
1582 1598 'hgext.fsmonitor.pywatchman.bser',
1583 1599 ['hgext/fsmonitor/pywatchman/bser.c'],
1584 1600 extra_compile_args=common_cflags,
1585 1601 ),
1586 1602 RustStandaloneExtension(
1587 1603 'mercurial.rustext',
1588 1604 'hg-cpython',
1589 1605 'librusthg',
1590 1606 ),
1591 1607 ]
1592 1608
1593 1609
1594 1610 sys.path.insert(0, 'contrib/python-zstandard')
1595 1611 import setup_zstd
1596 1612
1597 1613 zstd = setup_zstd.get_c_extension(
1598 1614 name='mercurial.zstd', root=os.path.abspath(os.path.dirname(__file__))
1599 1615 )
1600 1616 zstd.extra_compile_args += common_cflags
1601 1617 extmodules.append(zstd)
1602 1618
1603 1619 try:
1604 1620 from distutils import cygwinccompiler
1605 1621
1606 1622 # the -mno-cygwin option has been deprecated for years
1607 1623 mingw32compilerclass = cygwinccompiler.Mingw32CCompiler
1608 1624
1609 1625 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
1610 1626 def __init__(self, *args, **kwargs):
1611 1627 mingw32compilerclass.__init__(self, *args, **kwargs)
1612 1628 for i in 'compiler compiler_so linker_exe linker_so'.split():
1613 1629 try:
1614 1630 getattr(self, i).remove('-mno-cygwin')
1615 1631 except ValueError:
1616 1632 pass
1617 1633
1618 1634 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
1619 1635 except ImportError:
1620 1636 # the cygwinccompiler package is not available on some Python
1621 1637 # distributions like the ones from the optware project for Synology
1622 1638 # DiskStation boxes
1623 1639 class HackedMingw32CCompiler:
1624 1640 pass
1625 1641
1626 1642
1627 1643 if os.name == 'nt':
1628 1644 # Allow compiler/linker flags to be added to Visual Studio builds. Passing
1629 1645 # extra_link_args to distutils.extensions.Extension() doesn't have any
1630 1646 # effect.
1631 1647 from distutils import msvccompiler
1632 1648
1633 1649 msvccompilerclass = msvccompiler.MSVCCompiler
1634 1650
1635 1651 class HackedMSVCCompiler(msvccompiler.MSVCCompiler):
1636 1652 def initialize(self):
1637 1653 msvccompilerclass.initialize(self)
1638 1654 # "warning LNK4197: export 'func' specified multiple times"
1639 1655 self.ldflags_shared.append('/ignore:4197')
1640 1656 self.ldflags_shared_debug.append('/ignore:4197')
1641 1657
1642 1658 msvccompiler.MSVCCompiler = HackedMSVCCompiler
1643 1659
1644 1660 packagedata = {
1645 1661 'mercurial': [
1646 1662 'configitems.toml',
1647 1663 'locale/*/LC_MESSAGES/hg.mo',
1648 1664 'dummycert.pem',
1649 1665 ],
1650 1666 'mercurial.defaultrc': [
1651 1667 '*.rc',
1652 1668 ],
1653 1669 'mercurial.helptext': [
1654 1670 '*.txt',
1655 1671 ],
1656 1672 'mercurial.helptext.internals': [
1657 1673 '*.txt',
1658 1674 ],
1659 1675 'mercurial.thirdparty.attr': [
1660 1676 '*.pyi',
1661 1677 'py.typed',
1662 1678 ],
1663 1679 }
1664 1680
1665 1681
1666 1682 def ordinarypath(p):
1667 1683 return p and p[0] != '.' and p[-1] != '~'
1668 1684
1669 1685
1670 1686 for root in ('templates',):
1671 1687 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
1672 1688 packagename = curdir.replace(os.sep, '.')
1673 1689 packagedata[packagename] = list(filter(ordinarypath, files))
1674 1690
1675 1691 datafiles = []
1676 1692
1677 1693 # distutils expects version to be str/unicode. Converting it to
1678 1694 # unicode on Python 2 still works because it won't contain any
1679 1695 # non-ascii bytes and will be implicitly converted back to bytes
1680 1696 # when operated on.
1681 1697 assert isinstance(version, str)
1682 1698 setupversion = version
1683 1699
1684 1700 extra = {}
1685 1701
1686 1702 py2exepackages = [
1687 1703 'hgdemandimport',
1688 1704 'hgext3rd',
1689 1705 'hgext',
1690 1706 'email',
1691 1707 # implicitly imported per module policy
1692 1708 # (cffi wouldn't be used as a frozen exe)
1693 1709 'mercurial.cext',
1694 1710 #'mercurial.cffi',
1695 1711 'mercurial.pure',
1696 1712 ]
1697 1713
1698 1714 py2exe_includes = []
1699 1715
1700 1716 py2exeexcludes = []
1701 1717 py2exedllexcludes = ['crypt32.dll']
1702 1718
1703 1719 if issetuptools:
1704 1720 extra['python_requires'] = supportedpy
1705 1721
1706 1722 if py2exeloaded:
1707 1723 extra['console'] = [
1708 1724 {
1709 1725 'script': 'hg',
1710 1726 'copyright': 'Copyright (C) 2005-2023 Olivia Mackall and others',
1711 1727 'product_version': version,
1712 1728 }
1713 1729 ]
1714 1730 # Sub command of 'build' because 'py2exe' does not handle sub_commands.
1715 1731 # Need to override hgbuild because it has a private copy of
1716 1732 # build.sub_commands.
1717 1733 hgbuild.sub_commands.insert(0, ('build_hgextindex', None))
1718 1734 # put dlls in sub directory so that they won't pollute PATH
1719 1735 extra['zipfile'] = 'lib/library.zip'
1720 1736
1721 1737 # We allow some configuration to be supplemented via environment
1722 1738 # variables. This is better than setup.cfg files because it allows
1723 1739 # supplementing configs instead of replacing them.
1724 1740 extrapackages = os.environ.get('HG_PY2EXE_EXTRA_PACKAGES')
1725 1741 if extrapackages:
1726 1742 py2exepackages.extend(extrapackages.split(' '))
1727 1743
1728 1744 extra_includes = os.environ.get('HG_PY2EXE_EXTRA_INCLUDES')
1729 1745 if extra_includes:
1730 1746 py2exe_includes.extend(extra_includes.split(' '))
1731 1747
1732 1748 excludes = os.environ.get('HG_PY2EXE_EXTRA_EXCLUDES')
1733 1749 if excludes:
1734 1750 py2exeexcludes.extend(excludes.split(' '))
1735 1751
1736 1752 dllexcludes = os.environ.get('HG_PY2EXE_EXTRA_DLL_EXCLUDES')
1737 1753 if dllexcludes:
1738 1754 py2exedllexcludes.extend(dllexcludes.split(' '))
1739 1755
1740 1756 if os.environ.get('PYOXIDIZER'):
1741 1757 hgbuild.sub_commands.insert(0, ('build_hgextindex', None))
1742 1758
1743 1759 if os.name == 'nt':
1744 1760 # Windows binary file versions for exe/dll files must have the
1745 1761 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
1746 1762 setupversion = setupversion.split(r'+', 1)[0]
1747 1763
1748 1764 setup(
1749 1765 name='mercurial',
1750 1766 version=setupversion,
1751 1767 author='Olivia Mackall and many others',
1752 1768 author_email='mercurial@mercurial-scm.org',
1753 1769 url='https://mercurial-scm.org/',
1754 1770 download_url='https://mercurial-scm.org/release/',
1755 1771 description=(
1756 1772 'Fast scalable distributed SCM (revision control, version '
1757 1773 'control) system'
1758 1774 ),
1759 1775 long_description=(
1760 1776 'Mercurial is a distributed SCM tool written in Python.'
1761 1777 ' It is used by a number of large projects that require'
1762 1778 ' fast, reliable distributed revision control, such as '
1763 1779 'Mozilla.'
1764 1780 ),
1765 1781 license='GNU GPLv2 or any later version',
1766 1782 classifiers=[
1767 1783 'Development Status :: 6 - Mature',
1768 1784 'Environment :: Console',
1769 1785 'Intended Audience :: Developers',
1770 1786 'Intended Audience :: System Administrators',
1771 1787 'License :: OSI Approved :: GNU General Public License (GPL)',
1772 1788 'Natural Language :: Danish',
1773 1789 'Natural Language :: English',
1774 1790 'Natural Language :: German',
1775 1791 'Natural Language :: Italian',
1776 1792 'Natural Language :: Japanese',
1777 1793 'Natural Language :: Portuguese (Brazilian)',
1778 1794 'Operating System :: Microsoft :: Windows',
1779 1795 'Operating System :: OS Independent',
1780 1796 'Operating System :: POSIX',
1781 1797 'Programming Language :: C',
1782 1798 'Programming Language :: Python',
1783 1799 'Topic :: Software Development :: Version Control',
1784 1800 ],
1785 1801 scripts=scripts,
1786 1802 packages=packages,
1787 1803 ext_modules=extmodules,
1788 1804 data_files=datafiles,
1789 1805 package_data=packagedata,
1790 1806 cmdclass=cmdclass,
1791 1807 distclass=hgdist,
1792 1808 options={
1793 1809 'py2exe': {
1794 1810 'bundle_files': 3,
1795 1811 'dll_excludes': py2exedllexcludes,
1796 1812 'includes': py2exe_includes,
1797 1813 'excludes': py2exeexcludes,
1798 1814 'packages': py2exepackages,
1799 1815 },
1800 1816 'bdist_mpkg': {
1801 1817 'zipdist': False,
1802 1818 'license': 'COPYING',
1803 1819 'readme': 'contrib/packaging/macosx/Readme.html',
1804 1820 'welcome': 'contrib/packaging/macosx/Welcome.html',
1805 1821 },
1806 1822 },
1807 1823 **extra
1808 1824 )
@@ -1,4081 +1,4082 b''
1 1 #!/usr/bin/env python3
2 2 #
3 3 # run-tests.py - Run a set of tests on Mercurial
4 4 #
5 5 # Copyright 2006 Olivia Mackall <olivia@selenic.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 # Modifying this script is tricky because it has many modes:
11 11 # - serial (default) vs parallel (-jN, N > 1)
12 12 # - no coverage (default) vs coverage (-c, -C, -s)
13 13 # - temp install (default) vs specific hg script (--with-hg, --local)
14 14 # - tests are a mix of shell scripts and Python scripts
15 15 #
16 16 # If you change this script, it is recommended that you ensure you
17 17 # haven't broken it by running it in various modes with a representative
18 18 # sample of test scripts. For example:
19 19 #
20 20 # 1) serial, no coverage, temp install:
21 21 # ./run-tests.py test-s*
22 22 # 2) serial, no coverage, local hg:
23 23 # ./run-tests.py --local test-s*
24 24 # 3) serial, coverage, temp install:
25 25 # ./run-tests.py -c test-s*
26 26 # 4) serial, coverage, local hg:
27 27 # ./run-tests.py -c --local test-s* # unsupported
28 28 # 5) parallel, no coverage, temp install:
29 29 # ./run-tests.py -j2 test-s*
30 30 # 6) parallel, no coverage, local hg:
31 31 # ./run-tests.py -j2 --local test-s*
32 32 # 7) parallel, coverage, temp install:
33 33 # ./run-tests.py -j2 -c test-s* # currently broken
34 34 # 8) parallel, coverage, local install:
35 35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
36 36 # 9) parallel, custom tmp dir:
37 37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
38 38 # 10) parallel, pure, tests that call run-tests:
39 39 # ./run-tests.py --pure `grep -l run-tests.py *.t`
40 40 #
41 41 # (You could use any subset of the tests: test-s* happens to match
42 42 # enough that it's worth doing parallel runs, few enough that it
43 43 # completes fairly quickly, includes both shell and Python scripts, and
44 44 # includes some scripts that run daemon processes.)
45 45
46 46
47 47 import argparse
48 48 import collections
49 49 import contextlib
50 50 import difflib
51 51
52 52 import errno
53 53 import functools
54 54 import json
55 55 import multiprocessing
56 56 import os
57 57 import platform
58 58 import queue
59 59 import random
60 60 import re
61 61 import shlex
62 62 import shutil
63 63 import signal
64 64 import socket
65 65 import subprocess
66 66 import sys
67 67 import sysconfig
68 68 import tempfile
69 69 import threading
70 70 import time
71 71 import unittest
72 72 import uuid
73 73 import xml.dom.minidom as minidom
74 74
75 75
76 76 if sys.version_info < (3, 5, 0):
77 77 print(
78 78 '%s is only supported on Python 3.5+, not %s'
79 79 % (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3]))
80 80 )
81 81 sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
82 82
83 83 MACOS = sys.platform == 'darwin'
84 84 WINDOWS = os.name == r'nt'
85 85 shellquote = shlex.quote
86 86
87 87
88 88 processlock = threading.Lock()
89 89
90 90 pygmentspresent = False
91 91 try: # is pygments installed
92 92 import pygments
93 93 import pygments.lexers as lexers
94 94 import pygments.lexer as lexer
95 95 import pygments.formatters as formatters
96 96 import pygments.token as token
97 97 import pygments.style as style
98 98
99 99 if WINDOWS:
100 100 hgpath = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
101 101 sys.path.append(hgpath)
102 102 try:
103 103 from mercurial import win32 # pytype: disable=import-error
104 104
105 105 # Don't check the result code because it fails on heptapod, but
106 106 # something is able to convert to color anyway.
107 107 win32.enablevtmode()
108 108 finally:
109 109 sys.path = sys.path[:-1]
110 110
111 111 pygmentspresent = True
112 112 difflexer = lexers.DiffLexer()
113 113 terminal256formatter = formatters.Terminal256Formatter()
114 114 except ImportError:
115 115 pass
116 116
117 117 if pygmentspresent:
118 118
119 119 class TestRunnerStyle(style.Style):
120 120 default_style = ""
121 121 skipped = token.string_to_tokentype("Token.Generic.Skipped")
122 122 failed = token.string_to_tokentype("Token.Generic.Failed")
123 123 skippedname = token.string_to_tokentype("Token.Generic.SName")
124 124 failedname = token.string_to_tokentype("Token.Generic.FName")
125 125 styles = {
126 126 skipped: '#e5e5e5',
127 127 skippedname: '#00ffff',
128 128 failed: '#7f0000',
129 129 failedname: '#ff0000',
130 130 }
131 131
132 132 class TestRunnerLexer(lexer.RegexLexer):
133 133 testpattern = r'[\w-]+\.(t|py)(#[a-zA-Z0-9_\-\.]+)?'
134 134 tokens = {
135 135 'root': [
136 136 (r'^Skipped', token.Generic.Skipped, 'skipped'),
137 137 (r'^Failed ', token.Generic.Failed, 'failed'),
138 138 (r'^ERROR: ', token.Generic.Failed, 'failed'),
139 139 ],
140 140 'skipped': [
141 141 (testpattern, token.Generic.SName),
142 142 (r':.*', token.Generic.Skipped),
143 143 ],
144 144 'failed': [
145 145 (testpattern, token.Generic.FName),
146 146 (r'(:| ).*', token.Generic.Failed),
147 147 ],
148 148 }
149 149
150 150 runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle)
151 151 runnerlexer = TestRunnerLexer()
152 152
153 153 origenviron = os.environ.copy()
154 154
155 155
156 156 def _sys2bytes(p):
157 157 if p is None:
158 158 return p
159 159 return p.encode('utf-8')
160 160
161 161
162 162 def _bytes2sys(p):
163 163 if p is None:
164 164 return p
165 165 return p.decode('utf-8')
166 166
167 167
168 original_env = os.environ.copy()
168 169 osenvironb = getattr(os, 'environb', None)
169 170 if osenvironb is None:
170 171 # Windows lacks os.environb, for instance. A proxy over the real thing
171 172 # instead of a copy allows the environment to be updated via bytes on
172 173 # all platforms.
173 174 class environbytes:
174 175 def __init__(self, strenv):
175 176 self.__len__ = strenv.__len__
176 177 self.clear = strenv.clear
177 178 self._strenv = strenv
178 179
179 180 def __getitem__(self, k):
180 181 v = self._strenv.__getitem__(_bytes2sys(k))
181 182 return _sys2bytes(v)
182 183
183 184 def __setitem__(self, k, v):
184 185 self._strenv.__setitem__(_bytes2sys(k), _bytes2sys(v))
185 186
186 187 def __delitem__(self, k):
187 188 self._strenv.__delitem__(_bytes2sys(k))
188 189
189 190 def __contains__(self, k):
190 191 return self._strenv.__contains__(_bytes2sys(k))
191 192
192 193 def __iter__(self):
193 194 return iter([_sys2bytes(k) for k in iter(self._strenv)])
194 195
195 196 def get(self, k, default=None):
196 197 v = self._strenv.get(_bytes2sys(k), _bytes2sys(default))
197 198 return _sys2bytes(v)
198 199
199 200 def pop(self, k, default=None):
200 201 v = self._strenv.pop(_bytes2sys(k), _bytes2sys(default))
201 202 return _sys2bytes(v)
202 203
203 204 osenvironb = environbytes(os.environ)
204 205
205 206 getcwdb = getattr(os, 'getcwdb')
206 207 if not getcwdb or WINDOWS:
207 208 getcwdb = lambda: _sys2bytes(os.getcwd())
208 209
209 210
210 211 if WINDOWS:
211 212 _getcwdb = getcwdb
212 213
213 214 def getcwdb():
214 215 cwd = _getcwdb()
215 216 if re.match(b'^[a-z]:', cwd):
216 217 # os.getcwd() is inconsistent on the capitalization of the drive
217 218 # letter, so adjust it. see https://bugs.python.org/issue40368
218 219 cwd = cwd[0:1].upper() + cwd[1:]
219 220 return cwd
220 221
221 222
222 223 # For Windows support
223 224 wifexited = getattr(os, "WIFEXITED", lambda x: False)
224 225
225 226 # Whether to use IPv6
226 227 def checksocketfamily(name, port=20058):
227 228 """return true if we can listen on localhost using family=name
228 229
229 230 name should be either 'AF_INET', or 'AF_INET6'.
230 231 port being used is okay - EADDRINUSE is considered as successful.
231 232 """
232 233 family = getattr(socket, name, None)
233 234 if family is None:
234 235 return False
235 236 try:
236 237 s = socket.socket(family, socket.SOCK_STREAM)
237 238 s.bind(('localhost', port))
238 239 s.close()
239 240 return True
240 241 except (socket.error, OSError) as exc:
241 242 if exc.errno == errno.EADDRINUSE:
242 243 return True
243 244 elif exc.errno in (
244 245 errno.EADDRNOTAVAIL,
245 246 errno.EPROTONOSUPPORT,
246 247 errno.EAFNOSUPPORT,
247 248 ):
248 249 return False
249 250 else:
250 251 raise
251 252 else:
252 253 return False
253 254
254 255
255 256 # useipv6 will be set by parseargs
256 257 useipv6 = None
257 258
258 259
259 260 def checkportisavailable(port):
260 261 """return true if a port seems free to bind on localhost"""
261 262 if useipv6:
262 263 family = socket.AF_INET6
263 264 else:
264 265 family = socket.AF_INET
265 266 try:
266 267 with contextlib.closing(socket.socket(family, socket.SOCK_STREAM)) as s:
267 268 s.bind(('localhost', port))
268 269 return True
269 270 except PermissionError:
270 271 return False
271 272 except socket.error as exc:
272 273 if WINDOWS and exc.errno == errno.WSAEACCES:
273 274 return False
274 275 if exc.errno not in (
275 276 errno.EADDRINUSE,
276 277 errno.EADDRNOTAVAIL,
277 278 errno.EPROTONOSUPPORT,
278 279 ):
279 280 raise
280 281 return False
281 282
282 283
283 284 closefds = os.name == 'posix'
284 285
285 286
286 287 def Popen4(cmd, wd, timeout, env=None):
287 288 processlock.acquire()
288 289 p = subprocess.Popen(
289 290 _bytes2sys(cmd),
290 291 shell=True,
291 292 bufsize=-1,
292 293 cwd=_bytes2sys(wd),
293 294 env=env,
294 295 close_fds=closefds,
295 296 stdin=subprocess.PIPE,
296 297 stdout=subprocess.PIPE,
297 298 stderr=subprocess.STDOUT,
298 299 )
299 300 processlock.release()
300 301
301 302 p.fromchild = p.stdout
302 303 p.tochild = p.stdin
303 304 p.childerr = p.stderr
304 305
305 306 p.timeout = False
306 307 if timeout:
307 308
308 309 def t():
309 310 start = time.time()
310 311 while time.time() - start < timeout and p.returncode is None:
311 312 time.sleep(0.1)
312 313 p.timeout = True
313 314 vlog('# Timout reached for process %d' % p.pid)
314 315 if p.returncode is None:
315 316 terminate(p)
316 317
317 318 threading.Thread(target=t).start()
318 319
319 320 return p
320 321
321 322
322 323 if sys.executable:
323 324 sysexecutable = sys.executable
324 325 elif os.environ.get('PYTHONEXECUTABLE'):
325 326 sysexecutable = os.environ['PYTHONEXECUTABLE']
326 327 elif os.environ.get('PYTHON'):
327 328 sysexecutable = os.environ['PYTHON']
328 329 else:
329 330 raise AssertionError('Could not find Python interpreter')
330 331
331 332 PYTHON = _sys2bytes(sysexecutable.replace('\\', '/'))
332 333 IMPL_PATH = b'PYTHONPATH'
333 334 if 'java' in sys.platform:
334 335 IMPL_PATH = b'JYTHONPATH'
335 336
336 337 default_defaults = {
337 338 'jobs': ('HGTEST_JOBS', multiprocessing.cpu_count()),
338 339 'timeout': ('HGTEST_TIMEOUT', 360),
339 340 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 1500),
340 341 'port': ('HGTEST_PORT', 20059),
341 342 'shell': ('HGTEST_SHELL', 'sh'),
342 343 }
343 344
344 345 defaults = default_defaults.copy()
345 346
346 347
347 348 def canonpath(path):
348 349 return os.path.realpath(os.path.expanduser(path))
349 350
350 351
351 352 def which(exe):
352 353 # shutil.which only accept bytes from 3.8
353 354 cmd = _bytes2sys(exe)
354 355 real_exec = shutil.which(cmd)
355 356 return _sys2bytes(real_exec)
356 357
357 358
358 359 def parselistfiles(files, listtype, warn=True):
359 360 entries = dict()
360 361 for filename in files:
361 362 try:
362 363 path = os.path.expanduser(os.path.expandvars(filename))
363 364 f = open(path, "rb")
364 365 except FileNotFoundError:
365 366 if warn:
366 367 print("warning: no such %s file: %s" % (listtype, filename))
367 368 continue
368 369
369 370 for line in f.readlines():
370 371 line = line.split(b'#', 1)[0].strip()
371 372 if line:
372 373 # Ensure path entries are compatible with os.path.relpath()
373 374 entries[os.path.normpath(line)] = filename
374 375
375 376 f.close()
376 377 return entries
377 378
378 379
379 380 def parsettestcases(path):
380 381 """read a .t test file, return a set of test case names
381 382
382 383 If path does not exist, return an empty set.
383 384 """
384 385 cases = []
385 386 try:
386 387 with open(path, 'rb') as f:
387 388 for l in f:
388 389 if l.startswith(b'#testcases '):
389 390 cases.append(sorted(l[11:].split()))
390 391 except FileNotFoundError:
391 392 pass
392 393 return cases
393 394
394 395
395 396 def getparser():
396 397 """Obtain the OptionParser used by the CLI."""
397 398 parser = argparse.ArgumentParser(usage='%(prog)s [options] [tests]')
398 399
399 400 selection = parser.add_argument_group('Test Selection')
400 401 selection.add_argument(
401 402 '--allow-slow-tests',
402 403 action='store_true',
403 404 help='allow extremely slow tests',
404 405 )
405 406 selection.add_argument(
406 407 "--blacklist",
407 408 action="append",
408 409 help="skip tests listed in the specified blacklist file",
409 410 )
410 411 selection.add_argument(
411 412 "--changed",
412 413 help="run tests that are changed in parent rev or working directory",
413 414 )
414 415 selection.add_argument(
415 416 "-k", "--keywords", help="run tests matching keywords"
416 417 )
417 418 selection.add_argument(
418 419 "-r", "--retest", action="store_true", help="retest failed tests"
419 420 )
420 421 selection.add_argument(
421 422 "--test-list",
422 423 action="append",
423 424 help="read tests to run from the specified file",
424 425 )
425 426 selection.add_argument(
426 427 "--whitelist",
427 428 action="append",
428 429 help="always run tests listed in the specified whitelist file",
429 430 )
430 431 selection.add_argument(
431 432 'tests', metavar='TESTS', nargs='*', help='Tests to run'
432 433 )
433 434
434 435 harness = parser.add_argument_group('Test Harness Behavior')
435 436 harness.add_argument(
436 437 '--bisect-repo',
437 438 metavar='bisect_repo',
438 439 help=(
439 440 "Path of a repo to bisect. Use together with " "--known-good-rev"
440 441 ),
441 442 )
442 443 harness.add_argument(
443 444 "-d",
444 445 "--debug",
445 446 action="store_true",
446 447 help="debug mode: write output of test scripts to console"
447 448 " rather than capturing and diffing it (disables timeout)",
448 449 )
449 450 harness.add_argument(
450 451 "-f",
451 452 "--first",
452 453 action="store_true",
453 454 help="exit on the first test failure",
454 455 )
455 456 harness.add_argument(
456 457 "-i",
457 458 "--interactive",
458 459 action="store_true",
459 460 help="prompt to accept changed output",
460 461 )
461 462 harness.add_argument(
462 463 "-j",
463 464 "--jobs",
464 465 type=int,
465 466 help="number of jobs to run in parallel"
466 467 " (default: $%s or %d)" % defaults['jobs'],
467 468 )
468 469 harness.add_argument(
469 470 "--keep-tmpdir",
470 471 action="store_true",
471 472 help="keep temporary directory after running tests",
472 473 )
473 474 harness.add_argument(
474 475 '--known-good-rev',
475 476 metavar="known_good_rev",
476 477 help=(
477 478 "Automatically bisect any failures using this "
478 479 "revision as a known-good revision."
479 480 ),
480 481 )
481 482 harness.add_argument(
482 483 "--list-tests",
483 484 action="store_true",
484 485 help="list tests instead of running them",
485 486 )
486 487 harness.add_argument(
487 488 "--loop", action="store_true", help="loop tests repeatedly"
488 489 )
489 490 harness.add_argument(
490 491 '--random', action="store_true", help='run tests in random order'
491 492 )
492 493 harness.add_argument(
493 494 '--order-by-runtime',
494 495 action="store_true",
495 496 help='run slowest tests first, according to .testtimes',
496 497 )
497 498 harness.add_argument(
498 499 "-p",
499 500 "--port",
500 501 type=int,
501 502 help="port on which servers should listen"
502 503 " (default: $%s or %d)" % defaults['port'],
503 504 )
504 505 harness.add_argument(
505 506 '--profile-runner',
506 507 action='store_true',
507 508 help='run statprof on run-tests',
508 509 )
509 510 harness.add_argument(
510 511 "-R", "--restart", action="store_true", help="restart at last error"
511 512 )
512 513 harness.add_argument(
513 514 "--runs-per-test",
514 515 type=int,
515 516 dest="runs_per_test",
516 517 help="run each test N times (default=1)",
517 518 default=1,
518 519 )
519 520 harness.add_argument(
520 521 "--shell", help="shell to use (default: $%s or %s)" % defaults['shell']
521 522 )
522 523 harness.add_argument(
523 524 '--showchannels', action='store_true', help='show scheduling channels'
524 525 )
525 526 harness.add_argument(
526 527 "--slowtimeout",
527 528 type=int,
528 529 help="kill errant slow tests after SLOWTIMEOUT seconds"
529 530 " (default: $%s or %d)" % defaults['slowtimeout'],
530 531 )
531 532 harness.add_argument(
532 533 "-t",
533 534 "--timeout",
534 535 type=int,
535 536 help="kill errant tests after TIMEOUT seconds"
536 537 " (default: $%s or %d)" % defaults['timeout'],
537 538 )
538 539 harness.add_argument(
539 540 "--tmpdir",
540 541 help="run tests in the given temporary directory"
541 542 " (implies --keep-tmpdir)",
542 543 )
543 544 harness.add_argument(
544 545 "-v", "--verbose", action="store_true", help="output verbose messages"
545 546 )
546 547
547 548 hgconf = parser.add_argument_group('Mercurial Configuration')
548 549 hgconf.add_argument(
549 550 "--chg",
550 551 action="store_true",
551 552 help="install and use chg wrapper in place of hg",
552 553 )
553 554 hgconf.add_argument(
554 555 "--chg-debug",
555 556 action="store_true",
556 557 help="show chg debug logs",
557 558 )
558 559 hgconf.add_argument(
559 560 "--rhg",
560 561 action="store_true",
561 562 help="install and use rhg Rust implementation in place of hg",
562 563 )
563 564 hgconf.add_argument(
564 565 "--pyoxidized",
565 566 action="store_true",
566 567 help="build the hg binary using pyoxidizer",
567 568 )
568 569 hgconf.add_argument("--compiler", help="compiler to build with")
569 570 hgconf.add_argument(
570 571 '--extra-config-opt',
571 572 action="append",
572 573 default=[],
573 574 help='set the given config opt in the test hgrc',
574 575 )
575 576 hgconf.add_argument(
576 577 "-l",
577 578 "--local",
578 579 action="store_true",
579 580 help="shortcut for --with-hg=<testdir>/../hg, "
580 581 "--with-rhg=<testdir>/../rust/target/release/rhg if --rhg is set, "
581 582 "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set",
582 583 )
583 584 hgconf.add_argument(
584 585 "--ipv6",
585 586 action="store_true",
586 587 help="prefer IPv6 to IPv4 for network related tests",
587 588 )
588 589 hgconf.add_argument(
589 590 "--pure",
590 591 action="store_true",
591 592 help="use pure Python code instead of C extensions",
592 593 )
593 594 hgconf.add_argument(
594 595 "--rust",
595 596 action="store_true",
596 597 help="use Rust code alongside C extensions",
597 598 )
598 599 hgconf.add_argument(
599 600 "--no-rust",
600 601 action="store_true",
601 602 help="do not use Rust code even if compiled",
602 603 )
603 604 hgconf.add_argument(
604 605 "--with-chg",
605 606 metavar="CHG",
606 607 help="use specified chg wrapper in place of hg",
607 608 )
608 609 hgconf.add_argument(
609 610 "--with-rhg",
610 611 metavar="RHG",
611 612 help="use specified rhg Rust implementation in place of hg",
612 613 )
613 614 hgconf.add_argument(
614 615 "--with-hg",
615 616 metavar="HG",
616 617 help="test using specified hg script rather than a "
617 618 "temporary installation",
618 619 )
619 620
620 621 reporting = parser.add_argument_group('Results Reporting')
621 622 reporting.add_argument(
622 623 "-C",
623 624 "--annotate",
624 625 action="store_true",
625 626 help="output files annotated with coverage",
626 627 )
627 628 reporting.add_argument(
628 629 "--color",
629 630 choices=["always", "auto", "never"],
630 631 default=os.environ.get('HGRUNTESTSCOLOR', 'auto'),
631 632 help="colorisation: always|auto|never (default: auto)",
632 633 )
633 634 reporting.add_argument(
634 635 "-c",
635 636 "--cover",
636 637 action="store_true",
637 638 help="print a test coverage report",
638 639 )
639 640 reporting.add_argument(
640 641 '--exceptions',
641 642 action='store_true',
642 643 help='log all exceptions and generate an exception report',
643 644 )
644 645 reporting.add_argument(
645 646 "-H",
646 647 "--htmlcov",
647 648 action="store_true",
648 649 help="create an HTML report of the coverage of the files",
649 650 )
650 651 reporting.add_argument(
651 652 "--json",
652 653 action="store_true",
653 654 help="store test result data in 'report.json' file",
654 655 )
655 656 reporting.add_argument(
656 657 "--outputdir",
657 658 help="directory to write error logs to (default=test directory)",
658 659 )
659 660 reporting.add_argument(
660 661 "-n", "--nodiff", action="store_true", help="skip showing test changes"
661 662 )
662 663 reporting.add_argument(
663 664 "-S",
664 665 "--noskips",
665 666 action="store_true",
666 667 help="don't report skip tests verbosely",
667 668 )
668 669 reporting.add_argument(
669 670 "--time", action="store_true", help="time how long each test takes"
670 671 )
671 672 reporting.add_argument("--view", help="external diff viewer")
672 673 reporting.add_argument(
673 674 "--xunit", help="record xunit results at specified path"
674 675 )
675 676
676 677 for option, (envvar, default) in defaults.items():
677 678 defaults[option] = type(default)(os.environ.get(envvar, default))
678 679 parser.set_defaults(**defaults)
679 680
680 681 return parser
681 682
682 683
683 684 def parseargs(args, parser):
684 685 """Parse arguments with our OptionParser and validate results."""
685 686 options = parser.parse_args(args)
686 687
687 688 # jython is always pure
688 689 if 'java' in sys.platform or '__pypy__' in sys.modules:
689 690 options.pure = True
690 691
691 692 if platform.python_implementation() != 'CPython' and options.rust:
692 693 parser.error('Rust extensions are only available with CPython')
693 694
694 695 if options.pure and options.rust:
695 696 parser.error('--rust cannot be used with --pure')
696 697
697 698 if options.rust and options.no_rust:
698 699 parser.error('--rust cannot be used with --no-rust')
699 700
700 701 if options.local:
701 702 if options.with_hg or options.with_rhg or options.with_chg:
702 703 parser.error(
703 704 '--local cannot be used with --with-hg or --with-rhg or --with-chg'
704 705 )
705 706 if options.pyoxidized:
706 707 parser.error('--pyoxidized does not work with --local (yet)')
707 708 testdir = os.path.dirname(_sys2bytes(canonpath(sys.argv[0])))
708 709 reporootdir = os.path.dirname(testdir)
709 710 pathandattrs = [(b'hg', 'with_hg')]
710 711 if options.chg:
711 712 pathandattrs.append((b'contrib/chg/chg', 'with_chg'))
712 713 if options.rhg:
713 714 pathandattrs.append((b'rust/target/release/rhg', 'with_rhg'))
714 715 for relpath, attr in pathandattrs:
715 716 binpath = os.path.join(reporootdir, relpath)
716 717 if not (WINDOWS or os.access(binpath, os.X_OK)):
717 718 parser.error(
718 719 '--local specified, but %r not found or '
719 720 'not executable' % binpath
720 721 )
721 722 setattr(options, attr, _bytes2sys(binpath))
722 723
723 724 if options.with_hg:
724 725 options.with_hg = canonpath(_sys2bytes(options.with_hg))
725 726 if not (
726 727 os.path.isfile(options.with_hg)
727 728 and os.access(options.with_hg, os.X_OK)
728 729 ):
729 730 parser.error('--with-hg must specify an executable hg script')
730 731 if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']:
731 732 msg = 'warning: --with-hg should specify an hg script, not: %s\n'
732 733 msg %= _bytes2sys(os.path.basename(options.with_hg))
733 734 sys.stderr.write(msg)
734 735 sys.stderr.flush()
735 736
736 737 if (options.chg or options.with_chg) and WINDOWS:
737 738 parser.error('chg does not work on %s' % os.name)
738 739 if (options.rhg or options.with_rhg) and WINDOWS:
739 740 parser.error('rhg does not work on %s' % os.name)
740 741 if options.pyoxidized and not (MACOS or WINDOWS):
741 742 parser.error('--pyoxidized is currently macOS and Windows only')
742 743 if options.with_chg:
743 744 options.chg = False # no installation to temporary location
744 745 options.with_chg = canonpath(_sys2bytes(options.with_chg))
745 746 if not (
746 747 os.path.isfile(options.with_chg)
747 748 and os.access(options.with_chg, os.X_OK)
748 749 ):
749 750 parser.error('--with-chg must specify a chg executable')
750 751 if options.with_rhg:
751 752 options.rhg = False # no installation to temporary location
752 753 options.with_rhg = canonpath(_sys2bytes(options.with_rhg))
753 754 if not (
754 755 os.path.isfile(options.with_rhg)
755 756 and os.access(options.with_rhg, os.X_OK)
756 757 ):
757 758 parser.error('--with-rhg must specify a rhg executable')
758 759 if options.chg and options.with_hg:
759 760 # chg shares installation location with hg
760 761 parser.error(
761 762 '--chg does not work when --with-hg is specified '
762 763 '(use --with-chg instead)'
763 764 )
764 765 if options.rhg and options.with_hg:
765 766 # rhg shares installation location with hg
766 767 parser.error(
767 768 '--rhg does not work when --with-hg is specified '
768 769 '(use --with-rhg instead)'
769 770 )
770 771 if options.rhg and options.chg:
771 772 parser.error('--rhg and --chg do not work together')
772 773
773 774 if options.color == 'always' and not pygmentspresent:
774 775 sys.stderr.write(
775 776 'warning: --color=always ignored because '
776 777 'pygments is not installed\n'
777 778 )
778 779
779 780 if options.bisect_repo and not options.known_good_rev:
780 781 parser.error("--bisect-repo cannot be used without --known-good-rev")
781 782
782 783 global useipv6
783 784 if options.ipv6:
784 785 useipv6 = checksocketfamily('AF_INET6')
785 786 else:
786 787 # only use IPv6 if IPv4 is unavailable and IPv6 is available
787 788 useipv6 = (not checksocketfamily('AF_INET')) and checksocketfamily(
788 789 'AF_INET6'
789 790 )
790 791
791 792 options.anycoverage = options.cover or options.annotate or options.htmlcov
792 793 if options.anycoverage:
793 794 try:
794 795 import coverage
795 796
796 797 coverage.__version__ # silence unused import warning
797 798 except ImportError:
798 799 parser.error('coverage options now require the coverage package')
799 800
800 801 if options.anycoverage and options.local:
801 802 # this needs some path mangling somewhere, I guess
802 803 parser.error(
803 804 "sorry, coverage options do not work when --local " "is specified"
804 805 )
805 806
806 807 if options.anycoverage and options.with_hg:
807 808 parser.error(
808 809 "sorry, coverage options do not work when --with-hg " "is specified"
809 810 )
810 811
811 812 global verbose
812 813 if options.verbose:
813 814 verbose = ''
814 815
815 816 if options.tmpdir:
816 817 options.tmpdir = canonpath(options.tmpdir)
817 818
818 819 if options.jobs < 1:
819 820 parser.error('--jobs must be positive')
820 821 if options.interactive and options.debug:
821 822 parser.error("-i/--interactive and -d/--debug are incompatible")
822 823 if options.debug:
823 824 if options.timeout != defaults['timeout']:
824 825 sys.stderr.write('warning: --timeout option ignored with --debug\n')
825 826 if options.slowtimeout != defaults['slowtimeout']:
826 827 sys.stderr.write(
827 828 'warning: --slowtimeout option ignored with --debug\n'
828 829 )
829 830 options.timeout = 0
830 831 options.slowtimeout = 0
831 832
832 833 if options.blacklist:
833 834 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
834 835 if options.whitelist:
835 836 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
836 837 else:
837 838 options.whitelisted = {}
838 839
839 840 if options.showchannels:
840 841 options.nodiff = True
841 842
842 843 return options
843 844
844 845
845 846 def rename(src, dst):
846 847 """Like os.rename(), trade atomicity and opened files friendliness
847 848 for existing destination support.
848 849 """
849 850 shutil.copy(src, dst)
850 851 os.remove(src)
851 852
852 853
853 854 def makecleanable(path):
854 855 """Try to fix directory permission recursively so that the entire tree
855 856 can be deleted"""
856 857 for dirpath, dirnames, _filenames in os.walk(path, topdown=True):
857 858 for d in dirnames:
858 859 p = os.path.join(dirpath, d)
859 860 try:
860 861 os.chmod(p, os.stat(p).st_mode & 0o777 | 0o700) # chmod u+rwx
861 862 except OSError:
862 863 pass
863 864
864 865
865 866 _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
866 867
867 868
868 869 def getdiff(expected, output, ref, err):
869 870 servefail = False
870 871 lines = []
871 872 for line in _unified_diff(expected, output, ref, err):
872 873 if line.startswith(b'+++') or line.startswith(b'---'):
873 874 line = line.replace(b'\\', b'/')
874 875 if line.endswith(b' \n'):
875 876 line = line[:-2] + b'\n'
876 877 lines.append(line)
877 878 if not servefail and line.startswith(
878 879 b'+ abort: child process failed to start'
879 880 ):
880 881 servefail = True
881 882
882 883 return servefail, lines
883 884
884 885
885 886 verbose = False
886 887
887 888
888 889 def vlog(*msg):
889 890 """Log only when in verbose mode."""
890 891 if verbose is False:
891 892 return
892 893
893 894 return log(*msg)
894 895
895 896
896 897 # Bytes that break XML even in a CDATA block: control characters 0-31
897 898 # sans \t, \n and \r
898 899 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
899 900
900 901 # Match feature conditionalized output lines in the form, capturing the feature
901 902 # list in group 2, and the preceeding line output in group 1:
902 903 #
903 904 # output..output (feature !)\n
904 905 optline = re.compile(br'(.*) \((.+?) !\)\n$')
905 906
906 907
907 908 def cdatasafe(data):
908 909 """Make a string safe to include in a CDATA block.
909 910
910 911 Certain control characters are illegal in a CDATA block, and
911 912 there's no way to include a ]]> in a CDATA either. This function
912 913 replaces illegal bytes with ? and adds a space between the ]] so
913 914 that it won't break the CDATA block.
914 915 """
915 916 return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
916 917
917 918
918 919 def log(*msg):
919 920 """Log something to stdout.
920 921
921 922 Arguments are strings to print.
922 923 """
923 924 with iolock:
924 925 if verbose:
925 926 print(verbose, end=' ')
926 927 for m in msg:
927 928 print(m, end=' ')
928 929 print()
929 930 sys.stdout.flush()
930 931
931 932
932 933 def highlightdiff(line, color):
933 934 if not color:
934 935 return line
935 936 assert pygmentspresent
936 937 return pygments.highlight(
937 938 line.decode('latin1'), difflexer, terminal256formatter
938 939 ).encode('latin1')
939 940
940 941
941 942 def highlightmsg(msg, color):
942 943 if not color:
943 944 return msg
944 945 assert pygmentspresent
945 946 return pygments.highlight(msg, runnerlexer, runnerformatter)
946 947
947 948
948 949 def terminate(proc):
949 950 """Terminate subprocess"""
950 951 vlog('# Terminating process %d' % proc.pid)
951 952 try:
952 953 proc.terminate()
953 954 except OSError:
954 955 pass
955 956
956 957
957 958 def killdaemons(pidfile):
958 959 import killdaemons as killmod
959 960
960 961 return killmod.killdaemons(pidfile, tryhard=False, remove=True, logfn=vlog)
961 962
962 963
963 964 # sysconfig is not thread-safe (https://github.com/python/cpython/issues/92452)
964 965 sysconfiglock = threading.Lock()
965 966
966 967
967 968 class Test(unittest.TestCase):
968 969 """Encapsulates a single, runnable test.
969 970
970 971 While this class conforms to the unittest.TestCase API, it differs in that
971 972 instances need to be instantiated manually. (Typically, unittest.TestCase
972 973 classes are instantiated automatically by scanning modules.)
973 974 """
974 975
975 976 # Status code reserved for skipped tests (used by hghave).
976 977 SKIPPED_STATUS = 80
977 978
978 979 def __init__(
979 980 self,
980 981 path,
981 982 outputdir,
982 983 tmpdir,
983 984 keeptmpdir=False,
984 985 debug=False,
985 986 first=False,
986 987 timeout=None,
987 988 startport=None,
988 989 extraconfigopts=None,
989 990 shell=None,
990 991 hgcommand=None,
991 992 slowtimeout=None,
992 993 usechg=False,
993 994 chgdebug=False,
994 995 useipv6=False,
995 996 ):
996 997 """Create a test from parameters.
997 998
998 999 path is the full path to the file defining the test.
999 1000
1000 1001 tmpdir is the main temporary directory to use for this test.
1001 1002
1002 1003 keeptmpdir determines whether to keep the test's temporary directory
1003 1004 after execution. It defaults to removal (False).
1004 1005
1005 1006 debug mode will make the test execute verbosely, with unfiltered
1006 1007 output.
1007 1008
1008 1009 timeout controls the maximum run time of the test. It is ignored when
1009 1010 debug is True. See slowtimeout for tests with #require slow.
1010 1011
1011 1012 slowtimeout overrides timeout if the test has #require slow.
1012 1013
1013 1014 startport controls the starting port number to use for this test. Each
1014 1015 test will reserve 3 port numbers for execution. It is the caller's
1015 1016 responsibility to allocate a non-overlapping port range to Test
1016 1017 instances.
1017 1018
1018 1019 extraconfigopts is an iterable of extra hgrc config options. Values
1019 1020 must have the form "key=value" (something understood by hgrc). Values
1020 1021 of the form "foo.key=value" will result in "[foo] key=value".
1021 1022
1022 1023 shell is the shell to execute tests in.
1023 1024 """
1024 1025 if timeout is None:
1025 1026 timeout = defaults['timeout']
1026 1027 if startport is None:
1027 1028 startport = defaults['port']
1028 1029 if slowtimeout is None:
1029 1030 slowtimeout = defaults['slowtimeout']
1030 1031 self.path = path
1031 1032 self.relpath = os.path.relpath(path)
1032 1033 self.bname = os.path.basename(path)
1033 1034 self.name = _bytes2sys(self.bname)
1034 1035 self._testdir = os.path.dirname(path)
1035 1036 self._outputdir = outputdir
1036 1037 self._tmpname = os.path.basename(path)
1037 1038 self.errpath = os.path.join(self._outputdir, b'%s.err' % self.bname)
1038 1039
1039 1040 self._threadtmp = tmpdir
1040 1041 self._keeptmpdir = keeptmpdir
1041 1042 self._debug = debug
1042 1043 self._first = first
1043 1044 self._timeout = timeout
1044 1045 self._slowtimeout = slowtimeout
1045 1046 self._startport = startport
1046 1047 self._extraconfigopts = extraconfigopts or []
1047 1048 self._shell = _sys2bytes(shell)
1048 1049 self._hgcommand = hgcommand or b'hg'
1049 1050 self._usechg = usechg
1050 1051 self._chgdebug = chgdebug
1051 1052 self._useipv6 = useipv6
1052 1053
1053 1054 self._aborted = False
1054 1055 self._daemonpids = []
1055 1056 self._finished = None
1056 1057 self._ret = None
1057 1058 self._out = None
1058 1059 self._skipped = None
1059 1060 self._testtmp = None
1060 1061 self._chgsockdir = None
1061 1062
1062 1063 self._refout = self.readrefout()
1063 1064
1064 1065 def readrefout(self):
1065 1066 """read reference output"""
1066 1067 # If we're not in --debug mode and reference output file exists,
1067 1068 # check test output against it.
1068 1069 if self._debug:
1069 1070 return None # to match "out is None"
1070 1071 elif os.path.exists(self.refpath):
1071 1072 with open(self.refpath, 'rb') as f:
1072 1073 return f.read().splitlines(True)
1073 1074 else:
1074 1075 return []
1075 1076
1076 1077 # needed to get base class __repr__ running
1077 1078 @property
1078 1079 def _testMethodName(self):
1079 1080 return self.name
1080 1081
1081 1082 def __str__(self):
1082 1083 return self.name
1083 1084
1084 1085 def shortDescription(self):
1085 1086 return self.name
1086 1087
1087 1088 def setUp(self):
1088 1089 """Tasks to perform before run()."""
1089 1090 self._finished = False
1090 1091 self._ret = None
1091 1092 self._out = None
1092 1093 self._skipped = None
1093 1094
1094 1095 try:
1095 1096 os.mkdir(self._threadtmp)
1096 1097 except FileExistsError:
1097 1098 pass
1098 1099
1099 1100 name = self._tmpname
1100 1101 self._testtmp = os.path.join(self._threadtmp, name)
1101 1102 os.mkdir(self._testtmp)
1102 1103
1103 1104 # Remove any previous output files.
1104 1105 if os.path.exists(self.errpath):
1105 1106 try:
1106 1107 os.remove(self.errpath)
1107 1108 except FileNotFoundError:
1108 1109 # We might have raced another test to clean up a .err file,
1109 1110 # so ignore FileNotFoundError when removing a previous .err
1110 1111 # file.
1111 1112 pass
1112 1113
1113 1114 if self._usechg:
1114 1115 self._chgsockdir = os.path.join(
1115 1116 self._threadtmp, b'%s.chgsock' % name
1116 1117 )
1117 1118 os.mkdir(self._chgsockdir)
1118 1119
1119 1120 def run(self, result):
1120 1121 """Run this test and report results against a TestResult instance."""
1121 1122 # This function is extremely similar to unittest.TestCase.run(). Once
1122 1123 # we require Python 2.7 (or at least its version of unittest), this
1123 1124 # function can largely go away.
1124 1125 self._result = result
1125 1126 result.startTest(self)
1126 1127 try:
1127 1128 try:
1128 1129 self.setUp()
1129 1130 except (KeyboardInterrupt, SystemExit):
1130 1131 self._aborted = True
1131 1132 raise
1132 1133 except Exception:
1133 1134 result.addError(self, sys.exc_info())
1134 1135 return
1135 1136
1136 1137 success = False
1137 1138 try:
1138 1139 self.runTest()
1139 1140 except KeyboardInterrupt:
1140 1141 self._aborted = True
1141 1142 raise
1142 1143 except unittest.SkipTest as e:
1143 1144 result.addSkip(self, str(e))
1144 1145 # The base class will have already counted this as a
1145 1146 # test we "ran", but we want to exclude skipped tests
1146 1147 # from those we count towards those run.
1147 1148 result.testsRun -= 1
1148 1149 except self.failureException as e:
1149 1150 # This differs from unittest in that we don't capture
1150 1151 # the stack trace. This is for historical reasons and
1151 1152 # this decision could be revisited in the future,
1152 1153 # especially for PythonTest instances.
1153 1154 if result.addFailure(self, str(e)):
1154 1155 success = True
1155 1156 except Exception:
1156 1157 result.addError(self, sys.exc_info())
1157 1158 else:
1158 1159 success = True
1159 1160
1160 1161 try:
1161 1162 self.tearDown()
1162 1163 except (KeyboardInterrupt, SystemExit):
1163 1164 self._aborted = True
1164 1165 raise
1165 1166 except Exception:
1166 1167 result.addError(self, sys.exc_info())
1167 1168 success = False
1168 1169
1169 1170 if success:
1170 1171 result.addSuccess(self)
1171 1172 finally:
1172 1173 result.stopTest(self, interrupted=self._aborted)
1173 1174
1174 1175 def runTest(self):
1175 1176 """Run this test instance.
1176 1177
1177 1178 This will return a tuple describing the result of the test.
1178 1179 """
1179 1180 env = self._getenv()
1180 1181 self._genrestoreenv(env)
1181 1182 self._daemonpids.append(env['DAEMON_PIDS'])
1182 1183 self._createhgrc(env['HGRCPATH'])
1183 1184
1184 1185 vlog('# Test', self.name)
1185 1186
1186 1187 ret, out = self._run(env)
1187 1188 self._finished = True
1188 1189 self._ret = ret
1189 1190 self._out = out
1190 1191
1191 1192 def describe(ret):
1192 1193 if ret < 0:
1193 1194 return 'killed by signal: %d' % -ret
1194 1195 return 'returned error code %d' % ret
1195 1196
1196 1197 self._skipped = False
1197 1198
1198 1199 if ret == self.SKIPPED_STATUS:
1199 1200 if out is None: # Debug mode, nothing to parse.
1200 1201 missing = ['unknown']
1201 1202 failed = None
1202 1203 else:
1203 1204 missing, failed = TTest.parsehghaveoutput(out)
1204 1205
1205 1206 if not missing:
1206 1207 missing = ['skipped']
1207 1208
1208 1209 if failed:
1209 1210 self.fail('hg have failed checking for %s' % failed[-1])
1210 1211 else:
1211 1212 self._skipped = True
1212 1213 raise unittest.SkipTest(missing[-1])
1213 1214 elif ret == 'timeout':
1214 1215 self.fail('timed out')
1215 1216 elif ret is False:
1216 1217 self.fail('no result code from test')
1217 1218 elif out != self._refout:
1218 1219 # Diff generation may rely on written .err file.
1219 1220 if (
1220 1221 (ret != 0 or out != self._refout)
1221 1222 and not self._skipped
1222 1223 and not self._debug
1223 1224 ):
1224 1225 with open(self.errpath, 'wb') as f:
1225 1226 for line in out:
1226 1227 f.write(line)
1227 1228
1228 1229 # The result object handles diff calculation for us.
1229 1230 with firstlock:
1230 1231 if self._result.addOutputMismatch(self, ret, out, self._refout):
1231 1232 # change was accepted, skip failing
1232 1233 return
1233 1234 if self._first:
1234 1235 global firsterror
1235 1236 firsterror = True
1236 1237
1237 1238 if ret:
1238 1239 msg = 'output changed and ' + describe(ret)
1239 1240 else:
1240 1241 msg = 'output changed'
1241 1242
1242 1243 self.fail(msg)
1243 1244 elif ret:
1244 1245 self.fail(describe(ret))
1245 1246
1246 1247 def tearDown(self):
1247 1248 """Tasks to perform after run()."""
1248 1249 for entry in self._daemonpids:
1249 1250 killdaemons(entry)
1250 1251 self._daemonpids = []
1251 1252
1252 1253 if self._keeptmpdir:
1253 1254 log(
1254 1255 '\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s'
1255 1256 % (
1256 1257 _bytes2sys(self._testtmp),
1257 1258 _bytes2sys(self._threadtmp),
1258 1259 )
1259 1260 )
1260 1261 else:
1261 1262 try:
1262 1263 shutil.rmtree(self._testtmp)
1263 1264 except OSError:
1264 1265 # unreadable directory may be left in $TESTTMP; fix permission
1265 1266 # and try again
1266 1267 makecleanable(self._testtmp)
1267 1268 shutil.rmtree(self._testtmp, True)
1268 1269 shutil.rmtree(self._threadtmp, True)
1269 1270
1270 1271 if self._usechg:
1271 1272 # chgservers will stop automatically after they find the socket
1272 1273 # files are deleted
1273 1274 shutil.rmtree(self._chgsockdir, True)
1274 1275
1275 1276 if (
1276 1277 (self._ret != 0 or self._out != self._refout)
1277 1278 and not self._skipped
1278 1279 and not self._debug
1279 1280 and self._out
1280 1281 ):
1281 1282 with open(self.errpath, 'wb') as f:
1282 1283 for line in self._out:
1283 1284 f.write(line)
1284 1285
1285 1286 vlog("# Ret was:", self._ret, '(%s)' % self.name)
1286 1287
1287 1288 def _run(self, env):
1288 1289 # This should be implemented in child classes to run tests.
1289 1290 raise unittest.SkipTest('unknown test type')
1290 1291
1291 1292 def abort(self):
1292 1293 """Terminate execution of this test."""
1293 1294 self._aborted = True
1294 1295
1295 1296 def _portmap(self, i):
1296 1297 offset = b'' if i == 0 else b'%d' % i
1297 1298 return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset)
1298 1299
1299 1300 def _getreplacements(self):
1300 1301 """Obtain a mapping of text replacements to apply to test output.
1301 1302
1302 1303 Test output needs to be normalized so it can be compared to expected
1303 1304 output. This function defines how some of that normalization will
1304 1305 occur.
1305 1306 """
1306 1307 r = [
1307 1308 # This list should be parallel to defineport in _getenv
1308 1309 self._portmap(0),
1309 1310 self._portmap(1),
1310 1311 self._portmap(2),
1311 1312 (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'),
1312 1313 (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'),
1313 1314 ]
1314 1315 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
1315 1316 if WINDOWS:
1316 1317 # JSON output escapes backslashes in Windows paths, so also catch a
1317 1318 # double-escape.
1318 1319 replaced = self._testtmp.replace(b'\\', br'\\')
1319 1320 r.append((self._escapepath(replaced), b'$STR_REPR_TESTTMP'))
1320 1321
1321 1322 replacementfile = os.path.join(self._testdir, b'common-pattern.py')
1322 1323
1323 1324 if os.path.exists(replacementfile):
1324 1325 data = {}
1325 1326 with open(replacementfile, mode='rb') as source:
1326 1327 # the intermediate 'compile' step help with debugging
1327 1328 code = compile(source.read(), replacementfile, 'exec')
1328 1329 exec(code, data)
1329 1330 for value in data.get('substitutions', ()):
1330 1331 if len(value) != 2:
1331 1332 msg = 'malformatted substitution in %s: %r'
1332 1333 msg %= (replacementfile, value)
1333 1334 raise ValueError(msg)
1334 1335 r.append(value)
1335 1336 return r
1336 1337
1337 1338 def _escapepath(self, p):
1338 1339 if WINDOWS:
1339 1340 return b''.join(
1340 1341 c.isalpha()
1341 1342 and b'[%s%s]' % (c.lower(), c.upper())
1342 1343 or c in b'/\\'
1343 1344 and br'[/\\]'
1344 1345 or c.isdigit()
1345 1346 and c
1346 1347 or b'\\' + c
1347 1348 for c in [p[i : i + 1] for i in range(len(p))]
1348 1349 )
1349 1350 else:
1350 1351 return re.escape(p)
1351 1352
1352 1353 def _localip(self):
1353 1354 if self._useipv6:
1354 1355 return b'::1'
1355 1356 else:
1356 1357 return b'127.0.0.1'
1357 1358
1358 1359 def _genrestoreenv(self, testenv):
1359 1360 """Generate a script that can be used by tests to restore the original
1360 1361 environment."""
1361 1362 # Put the restoreenv script inside self._threadtmp
1362 1363 scriptpath = os.path.join(self._threadtmp, b'restoreenv.sh')
1363 1364 testenv['HGTEST_RESTOREENV'] = _bytes2sys(scriptpath)
1364 1365
1365 1366 # Only restore environment variable names that the shell allows
1366 1367 # us to export.
1367 1368 name_regex = re.compile('^[a-zA-Z][a-zA-Z0-9_]*$')
1368 1369
1369 1370 # Do not restore these variables; otherwise tests would fail.
1370 1371 reqnames = {'PYTHON', 'TESTDIR', 'TESTTMP'}
1371 1372
1372 1373 with open(scriptpath, 'w') as envf:
1373 1374 for name, value in origenviron.items():
1374 1375 if not name_regex.match(name):
1375 1376 # Skip environment variables with unusual names not
1376 1377 # allowed by most shells.
1377 1378 continue
1378 1379 if name in reqnames:
1379 1380 continue
1380 1381 envf.write('%s=%s\n' % (name, shellquote(value)))
1381 1382
1382 1383 for name in testenv:
1383 1384 if name in origenviron or name in reqnames:
1384 1385 continue
1385 1386 envf.write('unset %s\n' % (name,))
1386 1387
1387 1388 def _getenv(self):
1388 1389 """Obtain environment variables to use during test execution."""
1389 1390
1390 1391 def defineport(i):
1391 1392 offset = '' if i == 0 else '%s' % i
1392 1393 env["HGPORT%s" % offset] = '%s' % (self._startport + i)
1393 1394
1394 1395 env = os.environ.copy()
1395 1396 with sysconfiglock:
1396 1397 env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or ''
1397 1398 env['HGEMITWARNINGS'] = '1'
1398 1399 env['TESTTMP'] = _bytes2sys(self._testtmp)
1399 1400 # the FORWARD_SLASH version is useful when running `sh` on non unix
1400 1401 # system (e.g. Windows)
1401 1402 env['TESTTMP_FORWARD_SLASH'] = env['TESTTMP'].replace(os.sep, '/')
1402 1403 uid_file = os.path.join(_bytes2sys(self._testtmp), 'UID')
1403 1404 env['HGTEST_UUIDFILE'] = uid_file
1404 1405 env['TESTNAME'] = self.name
1405 1406 env['HOME'] = _bytes2sys(self._testtmp)
1406 1407 if WINDOWS:
1407 1408 env['REALUSERPROFILE'] = env['USERPROFILE']
1408 1409 # py3.8+ ignores HOME: https://bugs.python.org/issue36264
1409 1410 env['USERPROFILE'] = env['HOME']
1410 1411 formated_timeout = _bytes2sys(b"%d" % default_defaults['timeout'][1])
1411 1412 env['HGTEST_TIMEOUT_DEFAULT'] = formated_timeout
1412 1413 env['HGTEST_TIMEOUT'] = _bytes2sys(b"%d" % self._timeout)
1413 1414 # This number should match portneeded in _getport
1414 1415 for port in range(3):
1415 1416 # This list should be parallel to _portmap in _getreplacements
1416 1417 defineport(port)
1417 1418 env["HGRCPATH"] = _bytes2sys(os.path.join(self._threadtmp, b'.hgrc'))
1418 1419 env["DAEMON_PIDS"] = _bytes2sys(
1419 1420 os.path.join(self._threadtmp, b'daemon.pids')
1420 1421 )
1421 1422 env["HGEDITOR"] = (
1422 1423 '"' + sysexecutable + '"' + ' -c "import sys; sys.exit(0)"'
1423 1424 )
1424 1425 env["HGUSER"] = "test"
1425 1426 env["HGENCODING"] = "ascii"
1426 1427 env["HGENCODINGMODE"] = "strict"
1427 1428 env["HGHOSTNAME"] = "test-hostname"
1428 1429 env['HGIPV6'] = str(int(self._useipv6))
1429 1430 # See contrib/catapipe.py for how to use this functionality.
1430 1431 if 'HGTESTCATAPULTSERVERPIPE' not in env:
1431 1432 # If we don't have HGTESTCATAPULTSERVERPIPE explicitly set, pull the
1432 1433 # non-test one in as a default, otherwise set to devnull
1433 1434 env['HGTESTCATAPULTSERVERPIPE'] = env.get(
1434 1435 'HGCATAPULTSERVERPIPE', os.devnull
1435 1436 )
1436 1437
1437 1438 extraextensions = []
1438 1439 for opt in self._extraconfigopts:
1439 1440 section, key = opt.split('.', 1)
1440 1441 if section != 'extensions':
1441 1442 continue
1442 1443 name = key.split('=', 1)[0]
1443 1444 extraextensions.append(name)
1444 1445
1445 1446 if extraextensions:
1446 1447 env['HGTESTEXTRAEXTENSIONS'] = ' '.join(extraextensions)
1447 1448
1448 1449 # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw
1449 1450 # IP addresses.
1450 1451 env['LOCALIP'] = _bytes2sys(self._localip())
1451 1452
1452 1453 # This has the same effect as Py_LegacyWindowsStdioFlag in exewrapper.c,
1453 1454 # but this is needed for testing python instances like dummyssh,
1454 1455 # dummysmtpd.py, and dumbhttp.py.
1455 1456 if WINDOWS:
1456 1457 env['PYTHONLEGACYWINDOWSSTDIO'] = '1'
1457 1458
1458 1459 # Modified HOME in test environment can confuse Rust tools. So set
1459 1460 # CARGO_HOME and RUSTUP_HOME automatically if a Rust toolchain is
1460 1461 # present and these variables aren't already defined.
1461 1462 cargo_home_path = os.path.expanduser('~/.cargo')
1462 1463 rustup_home_path = os.path.expanduser('~/.rustup')
1463 1464
1464 1465 if os.path.exists(cargo_home_path) and b'CARGO_HOME' not in osenvironb:
1465 1466 env['CARGO_HOME'] = cargo_home_path
1466 1467 if (
1467 1468 os.path.exists(rustup_home_path)
1468 1469 and b'RUSTUP_HOME' not in osenvironb
1469 1470 ):
1470 1471 env['RUSTUP_HOME'] = rustup_home_path
1471 1472
1472 1473 # Reset some environment variables to well-known values so that
1473 1474 # the tests produce repeatable output.
1474 1475 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
1475 1476 env['TZ'] = 'GMT'
1476 1477 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1477 1478 env['COLUMNS'] = '80'
1478 1479 env['TERM'] = 'xterm'
1479 1480
1480 1481 dropped = [
1481 1482 'CDPATH',
1482 1483 'CHGDEBUG',
1483 1484 'EDITOR',
1484 1485 'GREP_OPTIONS',
1485 1486 'HG',
1486 1487 'HGMERGE',
1487 1488 'HGPLAIN',
1488 1489 'HGPLAINEXCEPT',
1489 1490 'HGPROF',
1490 1491 'http_proxy',
1491 1492 'no_proxy',
1492 1493 'NO_PROXY',
1493 1494 'PAGER',
1494 1495 'VISUAL',
1495 1496 ]
1496 1497
1497 1498 for k in dropped:
1498 1499 if k in env:
1499 1500 del env[k]
1500 1501
1501 1502 # unset env related to hooks
1502 1503 for k in list(env):
1503 1504 if k.startswith('HG_'):
1504 1505 del env[k]
1505 1506
1506 1507 if self._usechg:
1507 1508 env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server')
1508 1509 if self._chgdebug:
1509 1510 env['CHGDEBUG'] = 'true'
1510 1511
1511 1512 return env
1512 1513
1513 1514 def _createhgrc(self, path):
1514 1515 """Create an hgrc file for this test."""
1515 1516 with open(path, 'wb') as hgrc:
1516 1517 hgrc.write(b'[ui]\n')
1517 1518 hgrc.write(b'slash = True\n')
1518 1519 hgrc.write(b'interactive = False\n')
1519 1520 hgrc.write(b'detailed-exit-code = True\n')
1520 1521 hgrc.write(b'merge = internal:merge\n')
1521 1522 hgrc.write(b'mergemarkers = detailed\n')
1522 1523 hgrc.write(b'promptecho = True\n')
1523 1524 dummyssh = os.path.join(self._testdir, b'dummyssh')
1524 1525 hgrc.write(b'ssh = "%s" "%s"\n' % (PYTHON, dummyssh))
1525 1526 hgrc.write(b'timeout.warn=15\n')
1526 1527 hgrc.write(b'[chgserver]\n')
1527 1528 hgrc.write(b'idletimeout=60\n')
1528 1529 hgrc.write(b'[defaults]\n')
1529 1530 hgrc.write(b'[devel]\n')
1530 1531 hgrc.write(b'all-warnings = true\n')
1531 1532 hgrc.write(b'default-date = 0 0\n')
1532 1533 hgrc.write(b'[largefiles]\n')
1533 1534 hgrc.write(
1534 1535 b'usercache = %s\n'
1535 1536 % (os.path.join(self._testtmp, b'.cache/largefiles'))
1536 1537 )
1537 1538 hgrc.write(b'[lfs]\n')
1538 1539 hgrc.write(
1539 1540 b'usercache = %s\n'
1540 1541 % (os.path.join(self._testtmp, b'.cache/lfs'))
1541 1542 )
1542 1543 hgrc.write(b'[web]\n')
1543 1544 hgrc.write(b'address = localhost\n')
1544 1545 hgrc.write(b'ipv6 = %r\n' % self._useipv6)
1545 1546 hgrc.write(b'server-header = testing stub value\n')
1546 1547
1547 1548 for opt in self._extraconfigopts:
1548 1549 section, key = _sys2bytes(opt).split(b'.', 1)
1549 1550 assert b'=' in key, (
1550 1551 'extra config opt %s must ' 'have an = for assignment' % opt
1551 1552 )
1552 1553 hgrc.write(b'[%s]\n%s\n' % (section, key))
1553 1554
1554 1555 def fail(self, msg):
1555 1556 # unittest differentiates between errored and failed.
1556 1557 # Failed is denoted by AssertionError (by default at least).
1557 1558 raise AssertionError(msg)
1558 1559
1559 1560 def _runcommand(self, cmd, env, normalizenewlines=False):
1560 1561 """Run command in a sub-process, capturing the output (stdout and
1561 1562 stderr).
1562 1563
1563 1564 Return a tuple (exitcode, output). output is None in debug mode.
1564 1565 """
1565 1566 if self._debug:
1566 1567 proc = subprocess.Popen(
1567 1568 _bytes2sys(cmd),
1568 1569 shell=True,
1569 1570 close_fds=closefds,
1570 1571 cwd=_bytes2sys(self._testtmp),
1571 1572 env=env,
1572 1573 )
1573 1574 ret = proc.wait()
1574 1575 return (ret, None)
1575 1576
1576 1577 proc = Popen4(cmd, self._testtmp, self._timeout, env)
1577 1578
1578 1579 def cleanup():
1579 1580 terminate(proc)
1580 1581 ret = proc.wait()
1581 1582 if ret == 0:
1582 1583 ret = signal.SIGTERM << 8
1583 1584 killdaemons(env['DAEMON_PIDS'])
1584 1585 return ret
1585 1586
1586 1587 proc.tochild.close()
1587 1588
1588 1589 try:
1589 1590 output = proc.fromchild.read()
1590 1591 except KeyboardInterrupt:
1591 1592 vlog('# Handling keyboard interrupt')
1592 1593 cleanup()
1593 1594 raise
1594 1595
1595 1596 ret = proc.wait()
1596 1597 if wifexited(ret):
1597 1598 ret = os.WEXITSTATUS(ret)
1598 1599
1599 1600 if proc.timeout:
1600 1601 ret = 'timeout'
1601 1602
1602 1603 if ret:
1603 1604 killdaemons(env['DAEMON_PIDS'])
1604 1605
1605 1606 for s, r in self._getreplacements():
1606 1607 output = re.sub(s, r, output)
1607 1608
1608 1609 if normalizenewlines:
1609 1610 output = output.replace(b'\r\n', b'\n')
1610 1611
1611 1612 return ret, output.splitlines(True)
1612 1613
1613 1614
1614 1615 class PythonTest(Test):
1615 1616 """A Python-based test."""
1616 1617
1617 1618 @property
1618 1619 def refpath(self):
1619 1620 return os.path.join(self._testdir, b'%s.out' % self.bname)
1620 1621
1621 1622 def _run(self, env):
1622 1623 # Quote the python(3) executable for Windows
1623 1624 cmd = b'"%s" "%s"' % (PYTHON, self.path)
1624 1625 vlog("# Running", cmd.decode("utf-8"))
1625 1626 result = self._runcommand(cmd, env, normalizenewlines=WINDOWS)
1626 1627 if self._aborted:
1627 1628 raise KeyboardInterrupt()
1628 1629
1629 1630 return result
1630 1631
1631 1632
1632 1633 # Some glob patterns apply only in some circumstances, so the script
1633 1634 # might want to remove (glob) annotations that otherwise should be
1634 1635 # retained.
1635 1636 checkcodeglobpats = [
1636 1637 # On Windows it looks like \ doesn't require a (glob), but we know
1637 1638 # better.
1638 1639 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
1639 1640 re.compile(br'^moving \S+/.*[^)]$'),
1640 1641 re.compile(br'^pulling from \$TESTTMP/.*[^)]$'),
1641 1642 # Not all platforms have 127.0.0.1 as loopback (though most do),
1642 1643 # so we always glob that too.
1643 1644 re.compile(br'.*\$LOCALIP.*$'),
1644 1645 ]
1645 1646
1646 1647 bchr = lambda x: bytes([x])
1647 1648
1648 1649 WARN_UNDEFINED = 1
1649 1650 WARN_YES = 2
1650 1651 WARN_NO = 3
1651 1652
1652 1653 MARK_OPTIONAL = b" (?)\n"
1653 1654
1654 1655
1655 1656 def isoptional(line):
1656 1657 return line.endswith(MARK_OPTIONAL)
1657 1658
1658 1659
1659 1660 class TTest(Test):
1660 1661 """A "t test" is a test backed by a .t file."""
1661 1662
1662 1663 SKIPPED_PREFIX = b'skipped: '
1663 1664 FAILED_PREFIX = b'hghave check failed: '
1664 1665 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
1665 1666
1666 1667 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
1667 1668 ESCAPEMAP = {bchr(i): br'\x%02x' % i for i in range(256)}
1668 1669 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
1669 1670
1670 1671 def __init__(self, path, *args, **kwds):
1671 1672 # accept an extra "case" parameter
1672 1673 case = kwds.pop('case', [])
1673 1674 self._case = case
1674 1675 self._allcases = {x for y in parsettestcases(path) for x in y}
1675 1676 super(TTest, self).__init__(path, *args, **kwds)
1676 1677 if case:
1677 1678 casepath = b'#'.join(case)
1678 1679 self.name = '%s#%s' % (self.name, _bytes2sys(casepath))
1679 1680 self.errpath = b'%s#%s.err' % (self.errpath[:-4], casepath)
1680 1681 self._tmpname += b'-%s' % casepath.replace(b'#', b'-')
1681 1682 self._have = {}
1682 1683
1683 1684 @property
1684 1685 def refpath(self):
1685 1686 return os.path.join(self._testdir, self.bname)
1686 1687
1687 1688 def _run(self, env):
1688 1689 with open(self.path, 'rb') as f:
1689 1690 lines = f.readlines()
1690 1691
1691 1692 # .t file is both reference output and the test input, keep reference
1692 1693 # output updated with the the test input. This avoids some race
1693 1694 # conditions where the reference output does not match the actual test.
1694 1695 if self._refout is not None:
1695 1696 self._refout = lines
1696 1697
1697 1698 salt, script, after, expected = self._parsetest(lines)
1698 1699
1699 1700 # Write out the generated script.
1700 1701 fname = b'%s.sh' % self._testtmp
1701 1702 with open(fname, 'wb') as f:
1702 1703 for l in script:
1703 1704 f.write(l)
1704 1705
1705 1706 cmd = b'%s "%s"' % (self._shell, fname)
1706 1707 vlog("# Running", cmd.decode("utf-8"))
1707 1708
1708 1709 exitcode, output = self._runcommand(cmd, env)
1709 1710
1710 1711 if self._aborted:
1711 1712 raise KeyboardInterrupt()
1712 1713
1713 1714 # Do not merge output if skipped. Return hghave message instead.
1714 1715 # Similarly, with --debug, output is None.
1715 1716 if exitcode == self.SKIPPED_STATUS or output is None:
1716 1717 return exitcode, output
1717 1718
1718 1719 return self._processoutput(exitcode, output, salt, after, expected)
1719 1720
1720 1721 def _hghave(self, reqs):
1721 1722 allreqs = b' '.join(reqs)
1722 1723
1723 1724 self._detectslow(reqs)
1724 1725
1725 1726 if allreqs in self._have:
1726 1727 return self._have.get(allreqs)
1727 1728
1728 1729 # TODO do something smarter when all other uses of hghave are gone.
1729 1730 runtestdir = osenvironb[b'RUNTESTDIR']
1730 1731 tdir = runtestdir.replace(b'\\', b'/')
1731 1732 proc = Popen4(
1732 1733 b'%s -c "%s/hghave %s"' % (self._shell, tdir, allreqs),
1733 1734 self._testtmp,
1734 1735 0,
1735 1736 self._getenv(),
1736 1737 )
1737 1738 stdout, stderr = proc.communicate()
1738 1739 ret = proc.wait()
1739 1740 if wifexited(ret):
1740 1741 ret = os.WEXITSTATUS(ret)
1741 1742 if ret == 2:
1742 1743 print(stdout.decode('utf-8'))
1743 1744 sys.exit(1)
1744 1745
1745 1746 if ret != 0:
1746 1747 self._have[allreqs] = (False, stdout)
1747 1748 return False, stdout
1748 1749
1749 1750 self._have[allreqs] = (True, None)
1750 1751 return True, None
1751 1752
1752 1753 def _detectslow(self, reqs):
1753 1754 """update the timeout of slow test when appropriate"""
1754 1755 if b'slow' in reqs:
1755 1756 self._timeout = self._slowtimeout
1756 1757
1757 1758 def _iftest(self, args):
1758 1759 # implements "#if"
1759 1760 reqs = []
1760 1761 for arg in args:
1761 1762 if arg.startswith(b'no-') and arg[3:] in self._allcases:
1762 1763 if arg[3:] in self._case:
1763 1764 return False
1764 1765 elif arg in self._allcases:
1765 1766 if arg not in self._case:
1766 1767 return False
1767 1768 else:
1768 1769 reqs.append(arg)
1769 1770 self._detectslow(reqs)
1770 1771 return self._hghave(reqs)[0]
1771 1772
1772 1773 def _parsetest(self, lines):
1773 1774 # We generate a shell script which outputs unique markers to line
1774 1775 # up script results with our source. These markers include input
1775 1776 # line number and the last return code.
1776 1777 salt = b"SALT%d" % time.time()
1777 1778
1778 1779 def addsalt(line, inpython):
1779 1780 if inpython:
1780 1781 script.append(b'%s %d 0\n' % (salt, line))
1781 1782 else:
1782 1783 script.append(b'echo %s %d $?\n' % (salt, line))
1783 1784
1784 1785 activetrace = []
1785 1786 session = str(uuid.uuid4()).encode('ascii')
1786 1787 hgcatapult = os.getenv('HGTESTCATAPULTSERVERPIPE') or os.getenv(
1787 1788 'HGCATAPULTSERVERPIPE'
1788 1789 )
1789 1790
1790 1791 def toggletrace(cmd=None):
1791 1792 if not hgcatapult or hgcatapult == os.devnull:
1792 1793 return
1793 1794
1794 1795 if activetrace:
1795 1796 script.append(
1796 1797 b'echo END %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1797 1798 % (session, activetrace[0])
1798 1799 )
1799 1800 if cmd is None:
1800 1801 return
1801 1802
1802 1803 if isinstance(cmd, str):
1803 1804 quoted = shellquote(cmd.strip())
1804 1805 else:
1805 1806 quoted = shellquote(cmd.strip().decode('utf8')).encode('utf8')
1806 1807 quoted = quoted.replace(b'\\', b'\\\\')
1807 1808 script.append(
1808 1809 b'echo START %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1809 1810 % (session, quoted)
1810 1811 )
1811 1812 activetrace[0:] = [quoted]
1812 1813
1813 1814 script = []
1814 1815
1815 1816 # After we run the shell script, we re-unify the script output
1816 1817 # with non-active parts of the source, with synchronization by our
1817 1818 # SALT line number markers. The after table contains the non-active
1818 1819 # components, ordered by line number.
1819 1820 after = {}
1820 1821
1821 1822 # Expected shell script output.
1822 1823 expected = {}
1823 1824
1824 1825 pos = prepos = -1
1825 1826
1826 1827 # The current stack of conditionnal section.
1827 1828 # Each relevant conditionnal section can have the following value:
1828 1829 # - True: we should run this block
1829 1830 # - False: we should skip this block
1830 1831 # - None: The parent block is skipped,
1831 1832 # (no branch of this one will ever run)
1832 1833 condition_stack = []
1833 1834
1834 1835 def run_line():
1835 1836 """return True if the current line should be run"""
1836 1837 if not condition_stack:
1837 1838 return True
1838 1839 return bool(condition_stack[-1])
1839 1840
1840 1841 def push_conditional_block(should_run):
1841 1842 """Push a new conditional context, with its initial state
1842 1843
1843 1844 i.e. entry a #if block"""
1844 1845 if not run_line():
1845 1846 condition_stack.append(None)
1846 1847 else:
1847 1848 condition_stack.append(should_run)
1848 1849
1849 1850 def flip_conditional():
1850 1851 """reverse the current condition state
1851 1852
1852 1853 i.e. enter a #else
1853 1854 """
1854 1855 assert condition_stack
1855 1856 if condition_stack[-1] is not None:
1856 1857 condition_stack[-1] = not condition_stack[-1]
1857 1858
1858 1859 def pop_conditional():
1859 1860 """exit the current skipping context
1860 1861
1861 1862 i.e. reach the #endif"""
1862 1863 assert condition_stack
1863 1864 condition_stack.pop()
1864 1865
1865 1866 # We keep track of whether or not we're in a Python block so we
1866 1867 # can generate the surrounding doctest magic.
1867 1868 inpython = False
1868 1869
1869 1870 if self._debug:
1870 1871 script.append(b'set -x\n')
1871 1872 if os.getenv('MSYSTEM'):
1872 1873 script.append(b'alias pwd="pwd -W"\n')
1873 1874
1874 1875 if hgcatapult and hgcatapult != os.devnull:
1875 1876 hgcatapult = hgcatapult.encode('utf8')
1876 1877 cataname = self.name.encode('utf8')
1877 1878
1878 1879 # Kludge: use a while loop to keep the pipe from getting
1879 1880 # closed by our echo commands. The still-running file gets
1880 1881 # reaped at the end of the script, which causes the while
1881 1882 # loop to exit and closes the pipe. Sigh.
1882 1883 script.append(
1883 1884 b'rtendtracing() {\n'
1884 1885 b' echo END %(session)s %(name)s >> %(catapult)s\n'
1885 1886 b' rm -f "$TESTTMP/.still-running"\n'
1886 1887 b'}\n'
1887 1888 b'trap "rtendtracing" 0\n'
1888 1889 b'touch "$TESTTMP/.still-running"\n'
1889 1890 b'while [ -f "$TESTTMP/.still-running" ]; do sleep 1; done '
1890 1891 b'> %(catapult)s &\n'
1891 1892 b'HGCATAPULTSESSION=%(session)s ; export HGCATAPULTSESSION\n'
1892 1893 b'echo START %(session)s %(name)s >> %(catapult)s\n'
1893 1894 % {
1894 1895 b'name': cataname,
1895 1896 b'session': session,
1896 1897 b'catapult': hgcatapult,
1897 1898 }
1898 1899 )
1899 1900
1900 1901 if self._case:
1901 1902 casestr = b'#'.join(self._case)
1902 1903 if isinstance(casestr, str):
1903 1904 quoted = shellquote(casestr)
1904 1905 else:
1905 1906 quoted = shellquote(casestr.decode('utf8')).encode('utf8')
1906 1907 script.append(b'TESTCASE=%s\n' % quoted)
1907 1908 script.append(b'export TESTCASE\n')
1908 1909
1909 1910 n = 0
1910 1911 for n, l in enumerate(lines):
1911 1912 if not l.endswith(b'\n'):
1912 1913 l += b'\n'
1913 1914 if l.startswith(b'#require'):
1914 1915 lsplit = l.split()
1915 1916 if len(lsplit) < 2 or lsplit[0] != b'#require':
1916 1917 after.setdefault(pos, []).append(
1917 1918 b' !!! invalid #require\n'
1918 1919 )
1919 1920 if run_line():
1920 1921 haveresult, message = self._hghave(lsplit[1:])
1921 1922 if not haveresult:
1922 1923 script = [b'echo "%s"\nexit 80\n' % message]
1923 1924 break
1924 1925 after.setdefault(pos, []).append(l)
1925 1926 elif l.startswith(b'#if'):
1926 1927 lsplit = l.split()
1927 1928 if len(lsplit) < 2 or lsplit[0] != b'#if':
1928 1929 after.setdefault(pos, []).append(b' !!! invalid #if\n')
1929 1930 push_conditional_block(self._iftest(lsplit[1:]))
1930 1931 after.setdefault(pos, []).append(l)
1931 1932 elif l.startswith(b'#else'):
1932 1933 if not condition_stack:
1933 1934 after.setdefault(pos, []).append(b' !!! missing #if\n')
1934 1935 flip_conditional()
1935 1936 after.setdefault(pos, []).append(l)
1936 1937 elif l.startswith(b'#endif'):
1937 1938 if not condition_stack:
1938 1939 after.setdefault(pos, []).append(b' !!! missing #if\n')
1939 1940 pop_conditional()
1940 1941 after.setdefault(pos, []).append(l)
1941 1942 elif not run_line():
1942 1943 after.setdefault(pos, []).append(l)
1943 1944 elif l.startswith(b' >>> '): # python inlines
1944 1945 after.setdefault(pos, []).append(l)
1945 1946 prepos = pos
1946 1947 pos = n
1947 1948 if not inpython:
1948 1949 # We've just entered a Python block. Add the header.
1949 1950 inpython = True
1950 1951 addsalt(prepos, False) # Make sure we report the exit code.
1951 1952 script.append(b'"%s" -m heredoctest <<EOF\n' % PYTHON)
1952 1953 addsalt(n, True)
1953 1954 script.append(l[2:])
1954 1955 elif l.startswith(b' ... '): # python inlines
1955 1956 after.setdefault(prepos, []).append(l)
1956 1957 script.append(l[2:])
1957 1958 elif l.startswith(b' $ '): # commands
1958 1959 if inpython:
1959 1960 script.append(b'EOF\n')
1960 1961 inpython = False
1961 1962 after.setdefault(pos, []).append(l)
1962 1963 prepos = pos
1963 1964 pos = n
1964 1965 addsalt(n, False)
1965 1966 rawcmd = l[4:]
1966 1967 cmd = rawcmd.split()
1967 1968 toggletrace(rawcmd)
1968 1969 if len(cmd) == 2 and cmd[0] == b'cd':
1969 1970 rawcmd = b'cd %s || exit 1\n' % cmd[1]
1970 1971 script.append(rawcmd)
1971 1972 elif l.startswith(b' > '): # continuations
1972 1973 after.setdefault(prepos, []).append(l)
1973 1974 script.append(l[4:])
1974 1975 elif l.startswith(b' '): # results
1975 1976 # Queue up a list of expected results.
1976 1977 expected.setdefault(pos, []).append(l[2:])
1977 1978 else:
1978 1979 if inpython:
1979 1980 script.append(b'EOF\n')
1980 1981 inpython = False
1981 1982 # Non-command/result. Queue up for merged output.
1982 1983 after.setdefault(pos, []).append(l)
1983 1984
1984 1985 if inpython:
1985 1986 script.append(b'EOF\n')
1986 1987 if condition_stack:
1987 1988 after.setdefault(pos, []).append(b' !!! missing #endif\n')
1988 1989 addsalt(n + 1, False)
1989 1990 # Need to end any current per-command trace
1990 1991 if activetrace:
1991 1992 toggletrace()
1992 1993 return salt, script, after, expected
1993 1994
1994 1995 def _processoutput(self, exitcode, output, salt, after, expected):
1995 1996 # Merge the script output back into a unified test.
1996 1997 warnonly = WARN_UNDEFINED # 1: not yet; 2: yes; 3: for sure not
1997 1998 if exitcode != 0:
1998 1999 warnonly = WARN_NO
1999 2000
2000 2001 pos = -1
2001 2002 postout = []
2002 2003 for out_rawline in output:
2003 2004 out_line, cmd_line = out_rawline, None
2004 2005 if salt in out_rawline:
2005 2006 out_line, cmd_line = out_rawline.split(salt, 1)
2006 2007
2007 2008 pos, postout, warnonly = self._process_out_line(
2008 2009 out_line, pos, postout, expected, warnonly
2009 2010 )
2010 2011 pos, postout = self._process_cmd_line(cmd_line, pos, postout, after)
2011 2012
2012 2013 if pos in after:
2013 2014 postout += after.pop(pos)
2014 2015
2015 2016 if warnonly == WARN_YES:
2016 2017 exitcode = False # Set exitcode to warned.
2017 2018
2018 2019 return exitcode, postout
2019 2020
2020 2021 def _process_out_line(self, out_line, pos, postout, expected, warnonly):
2021 2022 while out_line:
2022 2023 if not out_line.endswith(b'\n'):
2023 2024 out_line += b' (no-eol)\n'
2024 2025
2025 2026 # Find the expected output at the current position.
2026 2027 els = [None]
2027 2028 if expected.get(pos, None):
2028 2029 els = expected[pos]
2029 2030
2030 2031 optional = []
2031 2032 for i, el in enumerate(els):
2032 2033 r = False
2033 2034 if el:
2034 2035 r, exact = self.linematch(el, out_line)
2035 2036 if isinstance(r, str):
2036 2037 if r == '-glob':
2037 2038 out_line = ''.join(el.rsplit(' (glob)', 1))
2038 2039 r = '' # Warn only this line.
2039 2040 elif r == "retry":
2040 2041 postout.append(b' ' + el)
2041 2042 else:
2042 2043 log('\ninfo, unknown linematch result: %r\n' % r)
2043 2044 r = False
2044 2045 if r:
2045 2046 els.pop(i)
2046 2047 break
2047 2048 if el:
2048 2049 if isoptional(el):
2049 2050 optional.append(i)
2050 2051 else:
2051 2052 m = optline.match(el)
2052 2053 if m:
2053 2054 conditions = [c for c in m.group(2).split(b' ')]
2054 2055
2055 2056 if not self._iftest(conditions):
2056 2057 optional.append(i)
2057 2058 if exact:
2058 2059 # Don't allow line to be matches against a later
2059 2060 # line in the output
2060 2061 els.pop(i)
2061 2062 break
2062 2063
2063 2064 if r:
2064 2065 if r == "retry":
2065 2066 continue
2066 2067 # clean up any optional leftovers
2067 2068 for i in optional:
2068 2069 postout.append(b' ' + els[i])
2069 2070 for i in reversed(optional):
2070 2071 del els[i]
2071 2072 postout.append(b' ' + el)
2072 2073 else:
2073 2074 if self.NEEDESCAPE(out_line):
2074 2075 out_line = TTest._stringescape(
2075 2076 b'%s (esc)\n' % out_line.rstrip(b'\n')
2076 2077 )
2077 2078 postout.append(b' ' + out_line) # Let diff deal with it.
2078 2079 if r != '': # If line failed.
2079 2080 warnonly = WARN_NO
2080 2081 elif warnonly == WARN_UNDEFINED:
2081 2082 warnonly = WARN_YES
2082 2083 break
2083 2084 else:
2084 2085 # clean up any optional leftovers
2085 2086 while expected.get(pos, None):
2086 2087 el = expected[pos].pop(0)
2087 2088 if el:
2088 2089 if not isoptional(el):
2089 2090 m = optline.match(el)
2090 2091 if m:
2091 2092 conditions = [c for c in m.group(2).split(b' ')]
2092 2093
2093 2094 if self._iftest(conditions):
2094 2095 # Don't append as optional line
2095 2096 continue
2096 2097 else:
2097 2098 continue
2098 2099 postout.append(b' ' + el)
2099 2100 return pos, postout, warnonly
2100 2101
2101 2102 def _process_cmd_line(self, cmd_line, pos, postout, after):
2102 2103 """process a "command" part of a line from unified test output"""
2103 2104 if cmd_line:
2104 2105 # Add on last return code.
2105 2106 ret = int(cmd_line.split()[1])
2106 2107 if ret != 0:
2107 2108 postout.append(b' [%d]\n' % ret)
2108 2109 if pos in after:
2109 2110 # Merge in non-active test bits.
2110 2111 postout += after.pop(pos)
2111 2112 pos = int(cmd_line.split()[0])
2112 2113 return pos, postout
2113 2114
2114 2115 @staticmethod
2115 2116 def rematch(el, l):
2116 2117 try:
2117 2118 # parse any flags at the beginning of the regex. Only 'i' is
2118 2119 # supported right now, but this should be easy to extend.
2119 2120 flags, el = re.match(br'^(\(\?i\))?(.*)', el).groups()[0:2]
2120 2121 flags = flags or b''
2121 2122 el = flags + b'(?:' + el + b')'
2122 2123 # use \Z to ensure that the regex matches to the end of the string
2123 2124 if WINDOWS:
2124 2125 return re.match(el + br'\r?\n\Z', l)
2125 2126 return re.match(el + br'\n\Z', l)
2126 2127 except re.error:
2127 2128 # el is an invalid regex
2128 2129 return False
2129 2130
2130 2131 @staticmethod
2131 2132 def globmatch(el, l):
2132 2133 # The only supported special characters are * and ? plus / which also
2133 2134 # matches \ on windows. Escaping of these characters is supported.
2134 2135 if el + b'\n' == l:
2135 2136 if os.altsep:
2136 2137 # matching on "/" is not needed for this line
2137 2138 for pat in checkcodeglobpats:
2138 2139 if pat.match(el):
2139 2140 return True
2140 2141 return b'-glob'
2141 2142 return True
2142 2143 el = el.replace(b'$LOCALIP', b'*')
2143 2144 i, n = 0, len(el)
2144 2145 res = b''
2145 2146 while i < n:
2146 2147 c = el[i : i + 1]
2147 2148 i += 1
2148 2149 if c == b'\\' and i < n and el[i : i + 1] in b'*?\\/':
2149 2150 res += el[i - 1 : i + 1]
2150 2151 i += 1
2151 2152 elif c == b'*':
2152 2153 res += b'.*'
2153 2154 elif c == b'?':
2154 2155 res += b'.'
2155 2156 elif c == b'/' and os.altsep:
2156 2157 res += b'[/\\\\]'
2157 2158 else:
2158 2159 res += re.escape(c)
2159 2160 return TTest.rematch(res, l)
2160 2161
2161 2162 def linematch(self, el, l):
2162 2163 if el == l: # perfect match (fast)
2163 2164 return True, True
2164 2165 retry = False
2165 2166 if isoptional(el):
2166 2167 retry = "retry"
2167 2168 el = el[: -len(MARK_OPTIONAL)] + b"\n"
2168 2169 else:
2169 2170 m = optline.match(el)
2170 2171 if m:
2171 2172 conditions = [c for c in m.group(2).split(b' ')]
2172 2173
2173 2174 el = m.group(1) + b"\n"
2174 2175 if not self._iftest(conditions):
2175 2176 # listed feature missing, should not match
2176 2177 return "retry", False
2177 2178
2178 2179 if el.endswith(b" (esc)\n"):
2179 2180 el = el[:-7].decode('unicode_escape') + '\n'
2180 2181 el = el.encode('latin-1')
2181 2182 if el == l or WINDOWS and el[:-1] + b'\r\n' == l:
2182 2183 return True, True
2183 2184 if el.endswith(b" (re)\n"):
2184 2185 return (TTest.rematch(el[:-6], l) or retry), False
2185 2186 if el.endswith(b" (glob)\n"):
2186 2187 # ignore '(glob)' added to l by 'replacements'
2187 2188 if l.endswith(b" (glob)\n"):
2188 2189 l = l[:-8] + b"\n"
2189 2190 return (TTest.globmatch(el[:-8], l) or retry), False
2190 2191 if os.altsep:
2191 2192 _l = l.replace(b'\\', b'/')
2192 2193 if el == _l or WINDOWS and el[:-1] + b'\r\n' == _l:
2193 2194 return True, True
2194 2195 return retry, True
2195 2196
2196 2197 @staticmethod
2197 2198 def parsehghaveoutput(lines):
2198 2199 """Parse hghave log lines.
2199 2200
2200 2201 Return tuple of lists (missing, failed):
2201 2202 * the missing/unknown features
2202 2203 * the features for which existence check failed"""
2203 2204 missing = []
2204 2205 failed = []
2205 2206 for line in lines:
2206 2207 if line.startswith(TTest.SKIPPED_PREFIX):
2207 2208 line = line.splitlines()[0]
2208 2209 missing.append(_bytes2sys(line[len(TTest.SKIPPED_PREFIX) :]))
2209 2210 elif line.startswith(TTest.FAILED_PREFIX):
2210 2211 line = line.splitlines()[0]
2211 2212 failed.append(_bytes2sys(line[len(TTest.FAILED_PREFIX) :]))
2212 2213
2213 2214 return missing, failed
2214 2215
2215 2216 @staticmethod
2216 2217 def _escapef(m):
2217 2218 return TTest.ESCAPEMAP[m.group(0)]
2218 2219
2219 2220 @staticmethod
2220 2221 def _stringescape(s):
2221 2222 return TTest.ESCAPESUB(TTest._escapef, s)
2222 2223
2223 2224
2224 2225 iolock = threading.RLock()
2225 2226 firstlock = threading.RLock()
2226 2227 firsterror = False
2227 2228
2228 2229 base_class = unittest.TextTestResult
2229 2230
2230 2231
2231 2232 class TestResult(base_class):
2232 2233 """Holds results when executing via unittest."""
2233 2234
2234 2235 def __init__(self, options, *args, **kwargs):
2235 2236 super(TestResult, self).__init__(*args, **kwargs)
2236 2237
2237 2238 self._options = options
2238 2239
2239 2240 # unittest.TestResult didn't have skipped until 2.7. We need to
2240 2241 # polyfill it.
2241 2242 self.skipped = []
2242 2243
2243 2244 # We have a custom "ignored" result that isn't present in any Python
2244 2245 # unittest implementation. It is very similar to skipped. It may make
2245 2246 # sense to map it into skip some day.
2246 2247 self.ignored = []
2247 2248
2248 2249 self.times = []
2249 2250 self._firststarttime = None
2250 2251 # Data stored for the benefit of generating xunit reports.
2251 2252 self.successes = []
2252 2253 self.faildata = {}
2253 2254
2254 2255 if options.color == 'auto':
2255 2256 isatty = self.stream.isatty()
2256 2257 # For some reason, redirecting stdout on Windows disables the ANSI
2257 2258 # color processing of stderr, which is what is used to print the
2258 2259 # output. Therefore, both must be tty on Windows to enable color.
2259 2260 if WINDOWS:
2260 2261 isatty = isatty and sys.stdout.isatty()
2261 2262 self.color = pygmentspresent and isatty
2262 2263 elif options.color == 'never':
2263 2264 self.color = False
2264 2265 else: # 'always', for testing purposes
2265 2266 self.color = pygmentspresent
2266 2267
2267 2268 def onStart(self, test):
2268 2269 """Can be overriden by custom TestResult"""
2269 2270
2270 2271 def onEnd(self):
2271 2272 """Can be overriden by custom TestResult"""
2272 2273
2273 2274 def addFailure(self, test, reason):
2274 2275 self.failures.append((test, reason))
2275 2276
2276 2277 if self._options.first:
2277 2278 self.stop()
2278 2279 else:
2279 2280 with iolock:
2280 2281 if reason == "timed out":
2281 2282 self.stream.write('t')
2282 2283 else:
2283 2284 if not self._options.nodiff:
2284 2285 self.stream.write('\n')
2285 2286 # Exclude the '\n' from highlighting to lex correctly
2286 2287 formatted = 'ERROR: %s output changed\n' % test
2287 2288 self.stream.write(highlightmsg(formatted, self.color))
2288 2289 self.stream.write('!')
2289 2290
2290 2291 self.stream.flush()
2291 2292
2292 2293 def addSuccess(self, test):
2293 2294 with iolock:
2294 2295 super(TestResult, self).addSuccess(test)
2295 2296 self.successes.append(test)
2296 2297
2297 2298 def addError(self, test, err):
2298 2299 super(TestResult, self).addError(test, err)
2299 2300 if self._options.first:
2300 2301 self.stop()
2301 2302
2302 2303 # Polyfill.
2303 2304 def addSkip(self, test, reason):
2304 2305 self.skipped.append((test, reason))
2305 2306 with iolock:
2306 2307 if self.showAll:
2307 2308 self.stream.writeln('skipped %s' % reason)
2308 2309 else:
2309 2310 self.stream.write('s')
2310 2311 self.stream.flush()
2311 2312
2312 2313 def addIgnore(self, test, reason):
2313 2314 self.ignored.append((test, reason))
2314 2315 with iolock:
2315 2316 if self.showAll:
2316 2317 self.stream.writeln('ignored %s' % reason)
2317 2318 else:
2318 2319 if reason not in ('not retesting', "doesn't match keyword"):
2319 2320 self.stream.write('i')
2320 2321 else:
2321 2322 self.testsRun += 1
2322 2323 self.stream.flush()
2323 2324
2324 2325 def addOutputMismatch(self, test, ret, got, expected):
2325 2326 """Record a mismatch in test output for a particular test."""
2326 2327 if self.shouldStop or firsterror:
2327 2328 # don't print, some other test case already failed and
2328 2329 # printed, we're just stale and probably failed due to our
2329 2330 # temp dir getting cleaned up.
2330 2331 return
2331 2332
2332 2333 accepted = False
2333 2334 lines = []
2334 2335
2335 2336 with iolock:
2336 2337 if self._options.nodiff:
2337 2338 pass
2338 2339 elif self._options.view:
2339 2340 v = self._options.view
2340 2341 subprocess.call(
2341 2342 r'"%s" "%s" "%s"'
2342 2343 % (v, _bytes2sys(test.refpath), _bytes2sys(test.errpath)),
2343 2344 shell=True,
2344 2345 )
2345 2346 else:
2346 2347 servefail, lines = getdiff(
2347 2348 expected, got, test.refpath, test.errpath
2348 2349 )
2349 2350 self.stream.write('\n')
2350 2351 for line in lines:
2351 2352 line = highlightdiff(line, self.color)
2352 2353 self.stream.flush()
2353 2354 self.stream.buffer.write(line)
2354 2355 self.stream.buffer.flush()
2355 2356
2356 2357 if servefail:
2357 2358 raise test.failureException(
2358 2359 'server failed to start (HGPORT=%s)' % test._startport
2359 2360 )
2360 2361
2361 2362 # handle interactive prompt without releasing iolock
2362 2363 if self._options.interactive:
2363 2364 if test.readrefout() != expected:
2364 2365 self.stream.write(
2365 2366 'Reference output has changed (run again to prompt '
2366 2367 'changes)'
2367 2368 )
2368 2369 else:
2369 2370 self.stream.write('Accept this change? [y/N] ')
2370 2371 self.stream.flush()
2371 2372 answer = sys.stdin.readline().strip()
2372 2373 if answer.lower() in ('y', 'yes'):
2373 2374 if test.path.endswith(b'.t'):
2374 2375 rename(test.errpath, test.path)
2375 2376 else:
2376 2377 rename(test.errpath, b'%s.out' % test.path)
2377 2378 accepted = True
2378 2379 if not accepted:
2379 2380 self.faildata[test.name] = b''.join(lines)
2380 2381
2381 2382 return accepted
2382 2383
2383 2384 def startTest(self, test):
2384 2385 super(TestResult, self).startTest(test)
2385 2386
2386 2387 # os.times module computes the user time and system time spent by
2387 2388 # child's processes along with real elapsed time taken by a process.
2388 2389 # This module has one limitation. It can only work for Linux user
2389 2390 # and not for Windows. Hence why we fall back to another function
2390 2391 # for wall time calculations.
2391 2392 test.started_times = os.times()
2392 2393 # TODO use a monotonic clock once support for Python 2.7 is dropped.
2393 2394 test.started_time = time.time()
2394 2395 if self._firststarttime is None: # thread racy but irrelevant
2395 2396 self._firststarttime = test.started_time
2396 2397
2397 2398 def stopTest(self, test, interrupted=False):
2398 2399 super(TestResult, self).stopTest(test)
2399 2400
2400 2401 test.stopped_times = os.times()
2401 2402 stopped_time = time.time()
2402 2403
2403 2404 starttime = test.started_times
2404 2405 endtime = test.stopped_times
2405 2406 origin = self._firststarttime
2406 2407 self.times.append(
2407 2408 (
2408 2409 test.name,
2409 2410 endtime[2] - starttime[2], # user space CPU time
2410 2411 endtime[3] - starttime[3], # sys space CPU time
2411 2412 stopped_time - test.started_time, # real time
2412 2413 test.started_time - origin, # start date in run context
2413 2414 stopped_time - origin, # end date in run context
2414 2415 )
2415 2416 )
2416 2417
2417 2418 if interrupted:
2418 2419 with iolock:
2419 2420 self.stream.writeln(
2420 2421 'INTERRUPTED: %s (after %d seconds)'
2421 2422 % (test.name, self.times[-1][3])
2422 2423 )
2423 2424
2424 2425
2425 2426 def getTestResult():
2426 2427 """
2427 2428 Returns the relevant test result
2428 2429 """
2429 2430 if "CUSTOM_TEST_RESULT" in os.environ:
2430 2431 testresultmodule = __import__(os.environ["CUSTOM_TEST_RESULT"])
2431 2432 return testresultmodule.TestResult
2432 2433 else:
2433 2434 return TestResult
2434 2435
2435 2436
2436 2437 class TestSuite(unittest.TestSuite):
2437 2438 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
2438 2439
2439 2440 def __init__(
2440 2441 self,
2441 2442 testdir,
2442 2443 jobs=1,
2443 2444 whitelist=None,
2444 2445 blacklist=None,
2445 2446 keywords=None,
2446 2447 loop=False,
2447 2448 runs_per_test=1,
2448 2449 loadtest=None,
2449 2450 showchannels=False,
2450 2451 *args,
2451 2452 **kwargs
2452 2453 ):
2453 2454 """Create a new instance that can run tests with a configuration.
2454 2455
2455 2456 testdir specifies the directory where tests are executed from. This
2456 2457 is typically the ``tests`` directory from Mercurial's source
2457 2458 repository.
2458 2459
2459 2460 jobs specifies the number of jobs to run concurrently. Each test
2460 2461 executes on its own thread. Tests actually spawn new processes, so
2461 2462 state mutation should not be an issue.
2462 2463
2463 2464 If there is only one job, it will use the main thread.
2464 2465
2465 2466 whitelist and blacklist denote tests that have been whitelisted and
2466 2467 blacklisted, respectively. These arguments don't belong in TestSuite.
2467 2468 Instead, whitelist and blacklist should be handled by the thing that
2468 2469 populates the TestSuite with tests. They are present to preserve
2469 2470 backwards compatible behavior which reports skipped tests as part
2470 2471 of the results.
2471 2472
2472 2473 keywords denotes key words that will be used to filter which tests
2473 2474 to execute. This arguably belongs outside of TestSuite.
2474 2475
2475 2476 loop denotes whether to loop over tests forever.
2476 2477 """
2477 2478 super(TestSuite, self).__init__(*args, **kwargs)
2478 2479
2479 2480 self._jobs = jobs
2480 2481 self._whitelist = whitelist
2481 2482 self._blacklist = blacklist
2482 2483 self._keywords = keywords
2483 2484 self._loop = loop
2484 2485 self._runs_per_test = runs_per_test
2485 2486 self._loadtest = loadtest
2486 2487 self._showchannels = showchannels
2487 2488
2488 2489 def run(self, result):
2489 2490 # We have a number of filters that need to be applied. We do this
2490 2491 # here instead of inside Test because it makes the running logic for
2491 2492 # Test simpler.
2492 2493 tests = []
2493 2494 num_tests = [0]
2494 2495 for test in self._tests:
2495 2496
2496 2497 def get():
2497 2498 num_tests[0] += 1
2498 2499 if getattr(test, 'should_reload', False):
2499 2500 return self._loadtest(test, num_tests[0])
2500 2501 return test
2501 2502
2502 2503 if not os.path.exists(test.path):
2503 2504 result.addSkip(test, "Doesn't exist")
2504 2505 continue
2505 2506
2506 2507 is_whitelisted = self._whitelist and (
2507 2508 test.relpath in self._whitelist or test.bname in self._whitelist
2508 2509 )
2509 2510 if not is_whitelisted:
2510 2511 is_blacklisted = self._blacklist and (
2511 2512 test.relpath in self._blacklist
2512 2513 or test.bname in self._blacklist
2513 2514 )
2514 2515 if is_blacklisted:
2515 2516 result.addSkip(test, 'blacklisted')
2516 2517 continue
2517 2518 if self._keywords:
2518 2519 with open(test.path, 'rb') as f:
2519 2520 t = f.read().lower() + test.bname.lower()
2520 2521 ignored = False
2521 2522 for k in self._keywords.lower().split():
2522 2523 if k not in t:
2523 2524 result.addIgnore(test, "doesn't match keyword")
2524 2525 ignored = True
2525 2526 break
2526 2527
2527 2528 if ignored:
2528 2529 continue
2529 2530 for _ in range(self._runs_per_test):
2530 2531 tests.append(get())
2531 2532
2532 2533 runtests = list(tests)
2533 2534 done = queue.Queue()
2534 2535 running = 0
2535 2536
2536 2537 channels_lock = threading.Lock()
2537 2538 channels = [""] * self._jobs
2538 2539
2539 2540 def job(test, result):
2540 2541 with channels_lock:
2541 2542 for n, v in enumerate(channels):
2542 2543 if not v:
2543 2544 channel = n
2544 2545 break
2545 2546 else:
2546 2547 raise ValueError('Could not find output channel')
2547 2548 channels[channel] = "=" + test.name[5:].split(".")[0]
2548 2549
2549 2550 r = None
2550 2551 try:
2551 2552 test(result)
2552 2553 except KeyboardInterrupt:
2553 2554 pass
2554 2555 except: # re-raises
2555 2556 r = ('!', test, 'run-test raised an error, see traceback')
2556 2557 raise
2557 2558 finally:
2558 2559 try:
2559 2560 channels[channel] = ''
2560 2561 except IndexError:
2561 2562 pass
2562 2563 done.put(r)
2563 2564
2564 2565 def stat():
2565 2566 count = 0
2566 2567 while channels:
2567 2568 d = '\n%03s ' % count
2568 2569 for n, v in enumerate(channels):
2569 2570 if v:
2570 2571 d += v[0]
2571 2572 channels[n] = v[1:] or '.'
2572 2573 else:
2573 2574 d += ' '
2574 2575 d += ' '
2575 2576 with iolock:
2576 2577 sys.stdout.write(d + ' ')
2577 2578 sys.stdout.flush()
2578 2579 for x in range(10):
2579 2580 if channels:
2580 2581 time.sleep(0.1)
2581 2582 count += 1
2582 2583
2583 2584 stoppedearly = False
2584 2585
2585 2586 if self._showchannels:
2586 2587 statthread = threading.Thread(target=stat, name="stat")
2587 2588 statthread.start()
2588 2589
2589 2590 try:
2590 2591 while tests or running:
2591 2592 if not done.empty() or running == self._jobs or not tests:
2592 2593 try:
2593 2594 done.get(True, 1)
2594 2595 running -= 1
2595 2596 if result and result.shouldStop:
2596 2597 stoppedearly = True
2597 2598 break
2598 2599 except queue.Empty:
2599 2600 continue
2600 2601 if tests and not running == self._jobs:
2601 2602 test = tests.pop(0)
2602 2603 if self._loop:
2603 2604 if getattr(test, 'should_reload', False):
2604 2605 num_tests[0] += 1
2605 2606 tests.append(self._loadtest(test, num_tests[0]))
2606 2607 else:
2607 2608 tests.append(test)
2608 2609 if self._jobs == 1:
2609 2610 job(test, result)
2610 2611 else:
2611 2612 t = threading.Thread(
2612 2613 target=job, name=test.name, args=(test, result)
2613 2614 )
2614 2615 t.start()
2615 2616 running += 1
2616 2617
2617 2618 # If we stop early we still need to wait on started tests to
2618 2619 # finish. Otherwise, there is a race between the test completing
2619 2620 # and the test's cleanup code running. This could result in the
2620 2621 # test reporting incorrect.
2621 2622 if stoppedearly:
2622 2623 while running:
2623 2624 try:
2624 2625 done.get(True, 1)
2625 2626 running -= 1
2626 2627 except queue.Empty:
2627 2628 continue
2628 2629 except KeyboardInterrupt:
2629 2630 for test in runtests:
2630 2631 test.abort()
2631 2632
2632 2633 channels = []
2633 2634
2634 2635 return result
2635 2636
2636 2637
2637 2638 # Save the most recent 5 wall-clock runtimes of each test to a
2638 2639 # human-readable text file named .testtimes. Tests are sorted
2639 2640 # alphabetically, while times for each test are listed from oldest to
2640 2641 # newest.
2641 2642
2642 2643
2643 2644 def loadtimes(outputdir):
2644 2645 times = []
2645 2646 try:
2646 2647 with open(os.path.join(outputdir, b'.testtimes')) as fp:
2647 2648 for line in fp:
2648 2649 m = re.match('(.*?) ([0-9. ]+)', line)
2649 2650 times.append(
2650 2651 (m.group(1), [float(t) for t in m.group(2).split()])
2651 2652 )
2652 2653 except FileNotFoundError:
2653 2654 pass
2654 2655 return times
2655 2656
2656 2657
2657 2658 def savetimes(outputdir, result):
2658 2659 saved = dict(loadtimes(outputdir))
2659 2660 maxruns = 5
2660 2661 skipped = {str(t[0]) for t in result.skipped}
2661 2662 for tdata in result.times:
2662 2663 test, real = tdata[0], tdata[3]
2663 2664 if test not in skipped:
2664 2665 ts = saved.setdefault(test, [])
2665 2666 ts.append(real)
2666 2667 ts[:] = ts[-maxruns:]
2667 2668
2668 2669 fd, tmpname = tempfile.mkstemp(
2669 2670 prefix=b'.testtimes', dir=outputdir, text=True
2670 2671 )
2671 2672 with os.fdopen(fd, 'w') as fp:
2672 2673 for name, ts in sorted(saved.items()):
2673 2674 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
2674 2675 timepath = os.path.join(outputdir, b'.testtimes')
2675 2676 try:
2676 2677 os.unlink(timepath)
2677 2678 except OSError:
2678 2679 pass
2679 2680 try:
2680 2681 os.rename(tmpname, timepath)
2681 2682 except OSError:
2682 2683 pass
2683 2684
2684 2685
2685 2686 class TextTestRunner(unittest.TextTestRunner):
2686 2687 """Custom unittest test runner that uses appropriate settings."""
2687 2688
2688 2689 def __init__(self, runner, *args, **kwargs):
2689 2690 super(TextTestRunner, self).__init__(*args, **kwargs)
2690 2691
2691 2692 self._runner = runner
2692 2693
2693 2694 self._result = getTestResult()(
2694 2695 self._runner.options, self.stream, self.descriptions, self.verbosity
2695 2696 )
2696 2697
2697 2698 def listtests(self, test):
2698 2699 test = sorted(test, key=lambda t: t.name)
2699 2700
2700 2701 self._result.onStart(test)
2701 2702
2702 2703 for t in test:
2703 2704 print(t.name)
2704 2705 self._result.addSuccess(t)
2705 2706
2706 2707 if self._runner.options.xunit:
2707 2708 with open(self._runner.options.xunit, "wb") as xuf:
2708 2709 self._writexunit(self._result, xuf)
2709 2710
2710 2711 if self._runner.options.json:
2711 2712 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2712 2713 with open(jsonpath, 'w') as fp:
2713 2714 self._writejson(self._result, fp)
2714 2715
2715 2716 return self._result
2716 2717
2717 2718 def run(self, test):
2718 2719 self._result.onStart(test)
2719 2720 test(self._result)
2720 2721
2721 2722 failed = len(self._result.failures)
2722 2723 skipped = len(self._result.skipped)
2723 2724 ignored = len(self._result.ignored)
2724 2725
2725 2726 with iolock:
2726 2727 self.stream.writeln('')
2727 2728
2728 2729 if not self._runner.options.noskips:
2729 2730 for test, msg in sorted(
2730 2731 self._result.skipped, key=lambda s: s[0].name
2731 2732 ):
2732 2733 formatted = 'Skipped %s: %s\n' % (test.name, msg)
2733 2734 msg = highlightmsg(formatted, self._result.color)
2734 2735 self.stream.write(msg)
2735 2736 for test, msg in sorted(
2736 2737 self._result.failures, key=lambda f: f[0].name
2737 2738 ):
2738 2739 formatted = 'Failed %s: %s\n' % (test.name, msg)
2739 2740 self.stream.write(highlightmsg(formatted, self._result.color))
2740 2741 for test, msg in sorted(
2741 2742 self._result.errors, key=lambda e: e[0].name
2742 2743 ):
2743 2744 self.stream.writeln('Errored %s: %s' % (test.name, msg))
2744 2745
2745 2746 if self._runner.options.xunit:
2746 2747 with open(self._runner.options.xunit, "wb") as xuf:
2747 2748 self._writexunit(self._result, xuf)
2748 2749
2749 2750 if self._runner.options.json:
2750 2751 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2751 2752 with open(jsonpath, 'w') as fp:
2752 2753 self._writejson(self._result, fp)
2753 2754
2754 2755 self._runner._checkhglib('Tested')
2755 2756
2756 2757 savetimes(self._runner._outputdir, self._result)
2757 2758
2758 2759 if failed and self._runner.options.known_good_rev:
2759 2760 self._bisecttests(t for t, m in self._result.failures)
2760 2761 self.stream.writeln(
2761 2762 '# Ran %d tests, %d skipped, %d failed.'
2762 2763 % (self._result.testsRun, skipped + ignored, failed)
2763 2764 )
2764 2765 if failed:
2765 2766 self.stream.writeln(
2766 2767 'python hash seed: %s' % os.environ['PYTHONHASHSEED']
2767 2768 )
2768 2769 if self._runner.options.time:
2769 2770 self.printtimes(self._result.times)
2770 2771
2771 2772 if self._runner.options.exceptions:
2772 2773 exceptions = aggregateexceptions(
2773 2774 os.path.join(self._runner._outputdir, b'exceptions')
2774 2775 )
2775 2776
2776 2777 self.stream.writeln('Exceptions Report:')
2777 2778 self.stream.writeln(
2778 2779 '%d total from %d frames'
2779 2780 % (exceptions['total'], len(exceptions['exceptioncounts']))
2780 2781 )
2781 2782 combined = exceptions['combined']
2782 2783 for key in sorted(combined, key=combined.get, reverse=True):
2783 2784 frame, line, exc = key
2784 2785 totalcount, testcount, leastcount, leasttest = combined[key]
2785 2786
2786 2787 self.stream.writeln(
2787 2788 '%d (%d tests)\t%s: %s (%s - %d total)'
2788 2789 % (
2789 2790 totalcount,
2790 2791 testcount,
2791 2792 frame,
2792 2793 exc,
2793 2794 leasttest,
2794 2795 leastcount,
2795 2796 )
2796 2797 )
2797 2798
2798 2799 self.stream.flush()
2799 2800
2800 2801 return self._result
2801 2802
2802 2803 def _bisecttests(self, tests):
2803 2804 bisectcmd = ['hg', 'bisect']
2804 2805 bisectrepo = self._runner.options.bisect_repo
2805 2806 if bisectrepo:
2806 2807 bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
2807 2808
2808 2809 def pread(args):
2809 2810 env = os.environ.copy()
2810 2811 env['HGPLAIN'] = '1'
2811 2812 p = subprocess.Popen(
2812 2813 args, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=env
2813 2814 )
2814 2815 data = p.stdout.read()
2815 2816 p.wait()
2816 2817 return data
2817 2818
2818 2819 for test in tests:
2819 2820 pread(bisectcmd + ['--reset']),
2820 2821 pread(bisectcmd + ['--bad', '.'])
2821 2822 pread(bisectcmd + ['--good', self._runner.options.known_good_rev])
2822 2823 # TODO: we probably need to forward more options
2823 2824 # that alter hg's behavior inside the tests.
2824 2825 opts = ''
2825 2826 withhg = self._runner.options.with_hg
2826 2827 if withhg:
2827 2828 opts += ' --with-hg=%s ' % shellquote(_bytes2sys(withhg))
2828 2829 rtc = '%s %s %s %s' % (sysexecutable, sys.argv[0], opts, test)
2829 2830 data = pread(bisectcmd + ['--command', rtc])
2830 2831 m = re.search(
2831 2832 (
2832 2833 br'\nThe first (?P<goodbad>bad|good) revision '
2833 2834 br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
2834 2835 br'summary: +(?P<summary>[^\n]+)\n'
2835 2836 ),
2836 2837 data,
2837 2838 (re.MULTILINE | re.DOTALL),
2838 2839 )
2839 2840 if m is None:
2840 2841 self.stream.writeln(
2841 2842 'Failed to identify failure point for %s' % test
2842 2843 )
2843 2844 continue
2844 2845 dat = m.groupdict()
2845 2846 verb = 'broken' if dat['goodbad'] == b'bad' else 'fixed'
2846 2847 self.stream.writeln(
2847 2848 '%s %s by %s (%s)'
2848 2849 % (
2849 2850 test,
2850 2851 verb,
2851 2852 dat['node'].decode('ascii'),
2852 2853 dat['summary'].decode('utf8', 'ignore'),
2853 2854 )
2854 2855 )
2855 2856
2856 2857 def printtimes(self, times):
2857 2858 # iolock held by run
2858 2859 self.stream.writeln('# Producing time report')
2859 2860 times.sort(key=lambda t: (t[3]))
2860 2861 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
2861 2862 self.stream.writeln(
2862 2863 '%-7s %-7s %-7s %-7s %-7s %s'
2863 2864 % ('start', 'end', 'cuser', 'csys', 'real', 'Test')
2864 2865 )
2865 2866 for tdata in times:
2866 2867 test = tdata[0]
2867 2868 cuser, csys, real, start, end = tdata[1:6]
2868 2869 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
2869 2870
2870 2871 @staticmethod
2871 2872 def _writexunit(result, outf):
2872 2873 # See http://llg.cubic.org/docs/junit/ for a reference.
2873 2874 timesd = {t[0]: t[3] for t in result.times}
2874 2875 doc = minidom.Document()
2875 2876 s = doc.createElement('testsuite')
2876 2877 s.setAttribute('errors', "0") # TODO
2877 2878 s.setAttribute('failures', str(len(result.failures)))
2878 2879 s.setAttribute('name', 'run-tests')
2879 2880 s.setAttribute(
2880 2881 'skipped', str(len(result.skipped) + len(result.ignored))
2881 2882 )
2882 2883 s.setAttribute('tests', str(result.testsRun))
2883 2884 doc.appendChild(s)
2884 2885 for tc in result.successes:
2885 2886 t = doc.createElement('testcase')
2886 2887 t.setAttribute('name', tc.name)
2887 2888 tctime = timesd.get(tc.name)
2888 2889 if tctime is not None:
2889 2890 t.setAttribute('time', '%.3f' % tctime)
2890 2891 s.appendChild(t)
2891 2892 for tc, err in sorted(result.faildata.items()):
2892 2893 t = doc.createElement('testcase')
2893 2894 t.setAttribute('name', tc)
2894 2895 tctime = timesd.get(tc)
2895 2896 if tctime is not None:
2896 2897 t.setAttribute('time', '%.3f' % tctime)
2897 2898 # createCDATASection expects a unicode or it will
2898 2899 # convert using default conversion rules, which will
2899 2900 # fail if string isn't ASCII.
2900 2901 err = cdatasafe(err).decode('utf-8', 'replace')
2901 2902 cd = doc.createCDATASection(err)
2902 2903 # Use 'failure' here instead of 'error' to match errors = 0,
2903 2904 # failures = len(result.failures) in the testsuite element.
2904 2905 failelem = doc.createElement('failure')
2905 2906 failelem.setAttribute('message', 'output changed')
2906 2907 failelem.setAttribute('type', 'output-mismatch')
2907 2908 failelem.appendChild(cd)
2908 2909 t.appendChild(failelem)
2909 2910 s.appendChild(t)
2910 2911 for tc, message in result.skipped:
2911 2912 # According to the schema, 'skipped' has no attributes. So store
2912 2913 # the skip message as a text node instead.
2913 2914 t = doc.createElement('testcase')
2914 2915 t.setAttribute('name', tc.name)
2915 2916 binmessage = message.encode('utf-8')
2916 2917 message = cdatasafe(binmessage).decode('utf-8', 'replace')
2917 2918 cd = doc.createCDATASection(message)
2918 2919 skipelem = doc.createElement('skipped')
2919 2920 skipelem.appendChild(cd)
2920 2921 t.appendChild(skipelem)
2921 2922 s.appendChild(t)
2922 2923 outf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
2923 2924
2924 2925 @staticmethod
2925 2926 def _writejson(result, outf):
2926 2927 timesd = {}
2927 2928 for tdata in result.times:
2928 2929 test = tdata[0]
2929 2930 timesd[test] = tdata[1:]
2930 2931
2931 2932 outcome = {}
2932 2933 groups = [
2933 2934 ('success', ((tc, None) for tc in result.successes)),
2934 2935 ('failure', result.failures),
2935 2936 ('skip', result.skipped),
2936 2937 ]
2937 2938 for res, testcases in groups:
2938 2939 for tc, __ in testcases:
2939 2940 if tc.name in timesd:
2940 2941 diff = result.faildata.get(tc.name, b'')
2941 2942 try:
2942 2943 diff = diff.decode('unicode_escape')
2943 2944 except UnicodeDecodeError as e:
2944 2945 diff = '%r decoding diff, sorry' % e
2945 2946 tres = {
2946 2947 'result': res,
2947 2948 'time': ('%0.3f' % timesd[tc.name][2]),
2948 2949 'cuser': ('%0.3f' % timesd[tc.name][0]),
2949 2950 'csys': ('%0.3f' % timesd[tc.name][1]),
2950 2951 'start': ('%0.3f' % timesd[tc.name][3]),
2951 2952 'end': ('%0.3f' % timesd[tc.name][4]),
2952 2953 'diff': diff,
2953 2954 }
2954 2955 else:
2955 2956 # blacklisted test
2956 2957 tres = {'result': res}
2957 2958
2958 2959 outcome[tc.name] = tres
2959 2960 jsonout = json.dumps(
2960 2961 outcome, sort_keys=True, indent=4, separators=(',', ': ')
2961 2962 )
2962 2963 outf.writelines(("testreport =", jsonout))
2963 2964
2964 2965
2965 2966 def sorttests(testdescs, previoustimes, shuffle=False):
2966 2967 """Do an in-place sort of tests."""
2967 2968 if shuffle:
2968 2969 random.shuffle(testdescs)
2969 2970 return
2970 2971
2971 2972 if previoustimes:
2972 2973
2973 2974 def sortkey(f):
2974 2975 f = f['path']
2975 2976 if f in previoustimes:
2976 2977 # Use most recent time as estimate
2977 2978 return -(previoustimes[f][-1])
2978 2979 else:
2979 2980 # Default to a rather arbitrary value of 1 second for new tests
2980 2981 return -1.0
2981 2982
2982 2983 else:
2983 2984 # keywords for slow tests
2984 2985 slow = {
2985 2986 b'svn': 10,
2986 2987 b'cvs': 10,
2987 2988 b'hghave': 10,
2988 2989 b'largefiles-update': 10,
2989 2990 b'run-tests': 10,
2990 2991 b'corruption': 10,
2991 2992 b'race': 10,
2992 2993 b'i18n': 10,
2993 2994 b'check': 100,
2994 2995 b'gendoc': 100,
2995 2996 b'contrib-perf': 200,
2996 2997 b'merge-combination': 100,
2997 2998 }
2998 2999 perf = {}
2999 3000
3000 3001 def sortkey(f):
3001 3002 # run largest tests first, as they tend to take the longest
3002 3003 f = f['path']
3003 3004 try:
3004 3005 return perf[f]
3005 3006 except KeyError:
3006 3007 try:
3007 3008 val = -os.stat(f).st_size
3008 3009 except FileNotFoundError:
3009 3010 perf[f] = -1e9 # file does not exist, tell early
3010 3011 return -1e9
3011 3012 for kw, mul in slow.items():
3012 3013 if kw in f:
3013 3014 val *= mul
3014 3015 if f.endswith(b'.py'):
3015 3016 val /= 10.0
3016 3017 perf[f] = val / 1000.0
3017 3018 return perf[f]
3018 3019
3019 3020 testdescs.sort(key=sortkey)
3020 3021
3021 3022
3022 3023 class TestRunner:
3023 3024 """Holds context for executing tests.
3024 3025
3025 3026 Tests rely on a lot of state. This object holds it for them.
3026 3027 """
3027 3028
3028 3029 # Programs required to run tests.
3029 3030 REQUIREDTOOLS = [
3030 3031 b'diff',
3031 3032 b'grep',
3032 3033 b'unzip',
3033 3034 b'gunzip',
3034 3035 b'bunzip2',
3035 3036 b'sed',
3036 3037 ]
3037 3038
3038 3039 # Maps file extensions to test class.
3039 3040 TESTTYPES = [
3040 3041 (b'.py', PythonTest),
3041 3042 (b'.t', TTest),
3042 3043 ]
3043 3044
3044 3045 def __init__(self):
3045 3046 self.options = None
3046 3047 self._hgroot = None
3047 3048 self._testdir = None
3048 3049 self._outputdir = None
3049 3050 self._hgtmp = None
3050 3051 self._installdir = None
3051 3052 self._bindir = None
3052 3053 # a place for run-tests.py to generate executable it needs
3053 3054 self._custom_bin_dir = None
3054 3055 self._pythondir = None
3055 3056 # True if we had to infer the pythondir from --with-hg
3056 3057 self._pythondir_inferred = False
3057 3058 self._coveragefile = None
3058 3059 self._createdfiles = []
3059 3060 self._hgcommand = None
3060 3061 self._hgpath = None
3061 3062 self._portoffset = 0
3062 3063 self._ports = {}
3063 3064
3064 3065 def run(self, args, parser=None):
3065 3066 """Run the test suite."""
3066 3067 oldmask = os.umask(0o22)
3067 3068 try:
3068 3069 parser = parser or getparser()
3069 3070 options = parseargs(args, parser)
3070 3071 tests = [_sys2bytes(a) for a in options.tests]
3071 3072 if options.test_list is not None:
3072 3073 for listfile in options.test_list:
3073 3074 with open(listfile, 'rb') as f:
3074 3075 tests.extend(t for t in f.read().splitlines() if t)
3075 3076 self.options = options
3076 3077
3077 3078 self._checktools()
3078 3079 testdescs = self.findtests(tests)
3079 3080 if options.profile_runner:
3080 3081 import statprof
3081 3082
3082 3083 statprof.start()
3083 3084 result = self._run(testdescs)
3084 3085 if options.profile_runner:
3085 3086 statprof.stop()
3086 3087 statprof.display()
3087 3088 return result
3088 3089
3089 3090 finally:
3090 3091 os.umask(oldmask)
3091 3092
3092 3093 def _run(self, testdescs):
3093 3094 testdir = getcwdb()
3094 3095 # assume all tests in same folder for now
3095 3096 if testdescs:
3096 3097 pathname = os.path.dirname(testdescs[0]['path'])
3097 3098 if pathname:
3098 3099 testdir = os.path.join(testdir, pathname)
3099 3100 self._testdir = osenvironb[b'TESTDIR'] = testdir
3100 3101 osenvironb[b'TESTDIR_FORWARD_SLASH'] = osenvironb[b'TESTDIR'].replace(
3101 3102 os.sep.encode('ascii'), b'/'
3102 3103 )
3103 3104
3104 3105 if self.options.outputdir:
3105 3106 self._outputdir = canonpath(_sys2bytes(self.options.outputdir))
3106 3107 else:
3107 3108 self._outputdir = getcwdb()
3108 3109 if testdescs and pathname:
3109 3110 self._outputdir = os.path.join(self._outputdir, pathname)
3110 3111 previoustimes = {}
3111 3112 if self.options.order_by_runtime:
3112 3113 previoustimes = dict(loadtimes(self._outputdir))
3113 3114 sorttests(testdescs, previoustimes, shuffle=self.options.random)
3114 3115
3115 3116 if 'PYTHONHASHSEED' not in os.environ:
3116 3117 # use a random python hash seed all the time
3117 3118 # we do the randomness ourself to know what seed is used
3118 3119 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
3119 3120
3120 3121 # Rayon (Rust crate for multi-threading) will use all logical CPU cores
3121 3122 # by default, causing thrashing on high-cpu-count systems.
3122 3123 # Setting its limit to 3 during tests should still let us uncover
3123 3124 # multi-threading bugs while keeping the thrashing reasonable.
3124 3125 os.environ.setdefault("RAYON_NUM_THREADS", "3")
3125 3126
3126 3127 if self.options.tmpdir:
3127 3128 self.options.keep_tmpdir = True
3128 3129 tmpdir = _sys2bytes(self.options.tmpdir)
3129 3130 if os.path.exists(tmpdir):
3130 3131 # Meaning of tmpdir has changed since 1.3: we used to create
3131 3132 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
3132 3133 # tmpdir already exists.
3133 3134 print("error: temp dir %r already exists" % tmpdir)
3134 3135 return 1
3135 3136
3136 3137 os.makedirs(tmpdir)
3137 3138 else:
3138 3139 d = None
3139 3140 if WINDOWS:
3140 3141 # without this, we get the default temp dir location, but
3141 3142 # in all lowercase, which causes troubles with paths (issue3490)
3142 3143 d = osenvironb.get(b'TMP', None)
3143 3144 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
3144 3145
3145 3146 self._hgtmp = osenvironb[b'HGTMP'] = os.path.realpath(tmpdir)
3146 3147
3147 3148 self._custom_bin_dir = os.path.join(self._hgtmp, b'custom-bin')
3148 3149 os.makedirs(self._custom_bin_dir)
3149 3150
3150 3151 # detect and enforce an alternative way to specify rust extension usage
3151 3152 if (
3152 3153 not (self.options.pure or self.options.rust or self.options.no_rust)
3153 3154 and os.environ.get("HGWITHRUSTEXT") == "cpython"
3154 3155 ):
3155 3156 self.options.rust = True
3156 3157
3157 3158 if self.options.with_hg:
3158 3159 self._installdir = None
3159 3160 whg = self.options.with_hg
3160 3161 self._bindir = os.path.dirname(os.path.realpath(whg))
3161 3162 assert isinstance(self._bindir, bytes)
3162 3163 self._hgcommand = os.path.basename(whg)
3163 3164
3164 3165 normbin = os.path.normpath(os.path.abspath(whg))
3165 3166 normbin = normbin.replace(_sys2bytes(os.sep), b'/')
3166 3167
3167 3168 # Other Python scripts in the test harness need to
3168 3169 # `import mercurial`. If `hg` is a Python script, we assume
3169 3170 # the Mercurial modules are relative to its path and tell the tests
3170 3171 # to load Python modules from its directory.
3171 3172 with open(whg, 'rb') as fh:
3172 3173 initial = fh.read(1024)
3173 3174
3174 3175 if re.match(b'#!.*python', initial):
3175 3176 self._pythondir = self._bindir
3176 3177 # If it looks like our in-repo Rust binary, use the source root.
3177 3178 # This is a bit hacky. But rhg is still not supported outside the
3178 3179 # source directory. So until it is, do the simple thing.
3179 3180 elif re.search(b'/rust/target/[^/]+/hg', normbin):
3180 3181 self._pythondir = os.path.dirname(self._testdir)
3181 3182 # Fall back to the legacy behavior.
3182 3183 else:
3183 3184 self._pythondir = self._bindir
3184 3185 self._pythondir_inferred = True
3185 3186
3186 3187 else:
3187 3188 self._installdir = os.path.join(self._hgtmp, b"install")
3188 3189 self._bindir = os.path.join(self._installdir, b"bin")
3189 3190 self._hgcommand = b'hg'
3190 3191 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
3191 3192
3192 3193 # Force the use of hg.exe instead of relying on MSYS to recognize hg is
3193 3194 # a python script and feed it to python.exe. Legacy stdio is force
3194 3195 # enabled by hg.exe, and this is a more realistic way to launch hg
3195 3196 # anyway.
3196 3197 if WINDOWS and not self._hgcommand.endswith(b'.exe'):
3197 3198 self._hgcommand += b'.exe'
3198 3199
3199 3200 real_hg = os.path.join(self._bindir, self._hgcommand)
3200 3201 osenvironb[b'HGTEST_REAL_HG'] = real_hg
3201 3202 # set CHGHG, then replace "hg" command by "chg"
3202 3203 chgbindir = self._bindir
3203 3204 if self.options.chg or self.options.with_chg:
3204 3205 osenvironb[b'CHG_INSTALLED_AS_HG'] = b'1'
3205 3206 osenvironb[b'CHGHG'] = real_hg
3206 3207 else:
3207 3208 # drop flag for hghave
3208 3209 osenvironb.pop(b'CHG_INSTALLED_AS_HG', None)
3209 3210 if self.options.chg:
3210 3211 self._hgcommand = b'chg'
3211 3212 elif self.options.with_chg:
3212 3213 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
3213 3214 self._hgcommand = os.path.basename(self.options.with_chg)
3214 3215
3215 3216 # configure fallback and replace "hg" command by "rhg"
3216 3217 rhgbindir = self._bindir
3217 3218 if self.options.rhg or self.options.with_rhg:
3218 3219 # Affects hghave.py
3219 3220 osenvironb[b'RHG_INSTALLED_AS_HG'] = b'1'
3220 3221 # Affects configuration. Alternatives would be setting configuration through
3221 3222 # `$HGRCPATH` but some tests override that, or changing `_hgcommand` to include
3222 3223 # `--config` but that disrupts tests that print command lines and check expected
3223 3224 # output.
3224 3225 osenvironb[b'RHG_ON_UNSUPPORTED'] = b'fallback'
3225 3226 osenvironb[b'RHG_FALLBACK_EXECUTABLE'] = real_hg
3226 3227 else:
3227 3228 # drop flag for hghave
3228 3229 osenvironb.pop(b'RHG_INSTALLED_AS_HG', None)
3229 3230 if self.options.rhg:
3230 3231 self._hgcommand = b'rhg'
3231 3232 elif self.options.with_rhg:
3232 3233 rhgbindir = os.path.dirname(os.path.realpath(self.options.with_rhg))
3233 3234 self._hgcommand = os.path.basename(self.options.with_rhg)
3234 3235
3235 3236 if self.options.pyoxidized:
3236 3237 testdir = os.path.dirname(_sys2bytes(canonpath(sys.argv[0])))
3237 3238 reporootdir = os.path.dirname(testdir)
3238 3239 # XXX we should ideally install stuff instead of using the local build
3239 3240
3240 3241 exe = b'hg'
3241 3242 triple = b''
3242 3243
3243 3244 if WINDOWS:
3244 3245 triple = b'x86_64-pc-windows-msvc'
3245 3246 exe = b'hg.exe'
3246 3247 elif MACOS:
3247 3248 # TODO: support Apple silicon too
3248 3249 triple = b'x86_64-apple-darwin'
3249 3250
3250 3251 bin_path = b'build/pyoxidizer/%s/release/app/%s' % (triple, exe)
3251 3252 full_path = os.path.join(reporootdir, bin_path)
3252 3253 self._hgcommand = full_path
3253 3254 # Affects hghave.py
3254 3255 osenvironb[b'PYOXIDIZED_INSTALLED_AS_HG'] = b'1'
3255 3256 else:
3256 3257 osenvironb.pop(b'PYOXIDIZED_INSTALLED_AS_HG', None)
3257 3258
3258 3259 osenvironb[b"BINDIR"] = self._bindir
3259 3260 osenvironb[b"PYTHON"] = PYTHON
3260 3261
3261 3262 fileb = _sys2bytes(__file__)
3262 3263 runtestdir = os.path.abspath(os.path.dirname(fileb))
3263 3264 osenvironb[b'RUNTESTDIR'] = runtestdir
3264 3265 osenvironb[b'RUNTESTDIR_FORWARD_SLASH'] = runtestdir.replace(
3265 3266 os.sep.encode('ascii'), b'/'
3266 3267 )
3267 3268 sepb = _sys2bytes(os.pathsep)
3268 3269 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
3269 3270 if os.path.islink(__file__):
3270 3271 # test helper will likely be at the end of the symlink
3271 3272 realfile = os.path.realpath(fileb)
3272 3273 realdir = os.path.abspath(os.path.dirname(realfile))
3273 3274 path.insert(2, realdir)
3274 3275 if chgbindir != self._bindir:
3275 3276 path.insert(1, chgbindir)
3276 3277 if rhgbindir != self._bindir:
3277 3278 path.insert(1, rhgbindir)
3278 3279 if self._testdir != runtestdir:
3279 3280 path = [self._testdir] + path
3280 3281 path = [self._custom_bin_dir] + path
3281 3282 osenvironb[b"PATH"] = sepb.join(path)
3282 3283
3283 3284 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
3284 3285 # can run .../tests/run-tests.py test-foo where test-foo
3285 3286 # adds an extension to HGRC. Also include run-test.py directory to
3286 3287 # import modules like heredoctest.
3287 3288 pypath = [self._pythondir, self._testdir, runtestdir]
3288 3289
3289 3290 # Setting PYTHONPATH with an activated venv causes the modules installed
3290 3291 # in it to be ignored. Therefore, include the related paths in sys.path
3291 3292 # in PYTHONPATH.
3292 3293 virtual_env = osenvironb.get(b"VIRTUAL_ENV")
3293 3294 if virtual_env:
3294 3295 virtual_env = os.path.join(virtual_env, b'')
3295 3296 for p in sys.path:
3296 3297 p = _sys2bytes(p)
3297 3298 if p.startswith(virtual_env):
3298 3299 pypath.append(p)
3299 3300
3300 3301 # We have to augment PYTHONPATH, rather than simply replacing
3301 3302 # it, in case external libraries are only available via current
3302 3303 # PYTHONPATH. (In particular, the Subversion bindings on OS X
3303 3304 # are in /opt/subversion.)
3304 3305 oldpypath = osenvironb.get(IMPL_PATH)
3305 3306 if oldpypath:
3306 3307 pypath.append(oldpypath)
3307 3308 osenvironb[IMPL_PATH] = sepb.join(pypath)
3308 3309
3309 3310 if self.options.pure:
3310 3311 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
3311 3312 os.environ["HGMODULEPOLICY"] = "py"
3312 3313 if self.options.rust:
3313 3314 os.environ["HGMODULEPOLICY"] = "rust+c"
3314 3315 if self.options.no_rust:
3315 3316 current_policy = os.environ.get("HGMODULEPOLICY", "")
3316 3317 if current_policy.startswith("rust+"):
3317 3318 os.environ["HGMODULEPOLICY"] = current_policy[len("rust+") :]
3318 3319 os.environ.pop("HGWITHRUSTEXT", None)
3319 3320
3320 3321 if self.options.allow_slow_tests:
3321 3322 os.environ["HGTEST_SLOW"] = "slow"
3322 3323 elif 'HGTEST_SLOW' in os.environ:
3323 3324 del os.environ['HGTEST_SLOW']
3324 3325
3325 3326 self._coveragefile = os.path.join(self._testdir, b'.coverage')
3326 3327
3327 3328 if self.options.exceptions:
3328 3329 exceptionsdir = os.path.join(self._outputdir, b'exceptions')
3329 3330 try:
3330 3331 os.makedirs(exceptionsdir)
3331 3332 except FileExistsError:
3332 3333 pass
3333 3334
3334 3335 # Remove all existing exception reports.
3335 3336 for f in os.listdir(exceptionsdir):
3336 3337 os.unlink(os.path.join(exceptionsdir, f))
3337 3338
3338 3339 osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir
3339 3340 logexceptions = os.path.join(self._testdir, b'logexceptions.py')
3340 3341 self.options.extra_config_opt.append(
3341 3342 'extensions.logexceptions=%s' % logexceptions.decode('utf-8')
3342 3343 )
3343 3344
3344 3345 vlog("# Using TESTDIR", _bytes2sys(self._testdir))
3345 3346 vlog("# Using RUNTESTDIR", _bytes2sys(osenvironb[b'RUNTESTDIR']))
3346 3347 vlog("# Using HGTMP", _bytes2sys(self._hgtmp))
3347 3348 vlog("# Using PATH", os.environ["PATH"])
3348 3349 vlog(
3349 3350 "# Using",
3350 3351 _bytes2sys(IMPL_PATH),
3351 3352 _bytes2sys(osenvironb[IMPL_PATH]),
3352 3353 )
3353 3354 vlog("# Writing to directory", _bytes2sys(self._outputdir))
3354 3355
3355 3356 try:
3356 3357 return self._runtests(testdescs) or 0
3357 3358 finally:
3358 3359 time.sleep(0.1)
3359 3360 self._cleanup()
3360 3361
3361 3362 def findtests(self, args):
3362 3363 """Finds possible test files from arguments.
3363 3364
3364 3365 If you wish to inject custom tests into the test harness, this would
3365 3366 be a good function to monkeypatch or override in a derived class.
3366 3367 """
3367 3368 if not args:
3368 3369 if self.options.changed:
3369 3370 proc = Popen4(
3370 3371 b'hg st --rev "%s" -man0 .'
3371 3372 % _sys2bytes(self.options.changed),
3372 3373 None,
3373 3374 0,
3374 3375 )
3375 3376 stdout, stderr = proc.communicate()
3376 3377 args = stdout.strip(b'\0').split(b'\0')
3377 3378 else:
3378 3379 args = os.listdir(b'.')
3379 3380
3380 3381 expanded_args = []
3381 3382 for arg in args:
3382 3383 if os.path.isdir(arg):
3383 3384 if not arg.endswith(b'/'):
3384 3385 arg += b'/'
3385 3386 expanded_args.extend([arg + a for a in os.listdir(arg)])
3386 3387 else:
3387 3388 expanded_args.append(arg)
3388 3389 args = expanded_args
3389 3390
3390 3391 testcasepattern = re.compile(br'([\w-]+\.t|py)(?:#([a-zA-Z0-9_\-.#]+))')
3391 3392 tests = []
3392 3393 for t in args:
3393 3394 case = []
3394 3395
3395 3396 if not (
3396 3397 os.path.basename(t).startswith(b'test-')
3397 3398 and (t.endswith(b'.py') or t.endswith(b'.t'))
3398 3399 ):
3399 3400
3400 3401 m = testcasepattern.match(os.path.basename(t))
3401 3402 if m is not None:
3402 3403 t_basename, casestr = m.groups()
3403 3404 t = os.path.join(os.path.dirname(t), t_basename)
3404 3405 if casestr:
3405 3406 case = casestr.split(b'#')
3406 3407 else:
3407 3408 continue
3408 3409
3409 3410 if t.endswith(b'.t'):
3410 3411 # .t file may contain multiple test cases
3411 3412 casedimensions = parsettestcases(t)
3412 3413 if casedimensions:
3413 3414 cases = []
3414 3415
3415 3416 def addcases(case, casedimensions):
3416 3417 if not casedimensions:
3417 3418 cases.append(case)
3418 3419 else:
3419 3420 for c in casedimensions[0]:
3420 3421 addcases(case + [c], casedimensions[1:])
3421 3422
3422 3423 addcases([], casedimensions)
3423 3424 if case and case in cases:
3424 3425 cases = [case]
3425 3426 elif case:
3426 3427 # Ignore invalid cases
3427 3428 cases = []
3428 3429 else:
3429 3430 pass
3430 3431 tests += [{'path': t, 'case': c} for c in sorted(cases)]
3431 3432 else:
3432 3433 tests.append({'path': t})
3433 3434 else:
3434 3435 tests.append({'path': t})
3435 3436
3436 3437 if self.options.retest:
3437 3438 retest_args = []
3438 3439 for test in tests:
3439 3440 errpath = self._geterrpath(test)
3440 3441 if os.path.exists(errpath):
3441 3442 retest_args.append(test)
3442 3443 tests = retest_args
3443 3444 return tests
3444 3445
3445 3446 def _runtests(self, testdescs):
3446 3447 def _reloadtest(test, i):
3447 3448 # convert a test back to its description dict
3448 3449 desc = {'path': test.path}
3449 3450 case = getattr(test, '_case', [])
3450 3451 if case:
3451 3452 desc['case'] = case
3452 3453 return self._gettest(desc, i)
3453 3454
3454 3455 try:
3455 3456 if self.options.restart:
3456 3457 orig = list(testdescs)
3457 3458 while testdescs:
3458 3459 desc = testdescs[0]
3459 3460 errpath = self._geterrpath(desc)
3460 3461 if os.path.exists(errpath):
3461 3462 break
3462 3463 testdescs.pop(0)
3463 3464 if not testdescs:
3464 3465 print("running all tests")
3465 3466 testdescs = orig
3466 3467
3467 3468 tests = [self._gettest(d, i) for i, d in enumerate(testdescs)]
3468 3469 num_tests = len(tests) * self.options.runs_per_test
3469 3470
3470 3471 jobs = min(num_tests, self.options.jobs)
3471 3472
3472 3473 failed = False
3473 3474 kws = self.options.keywords
3474 3475 if kws is not None:
3475 3476 kws = kws.encode('utf-8')
3476 3477
3477 3478 suite = TestSuite(
3478 3479 self._testdir,
3479 3480 jobs=jobs,
3480 3481 whitelist=self.options.whitelisted,
3481 3482 blacklist=self.options.blacklist,
3482 3483 keywords=kws,
3483 3484 loop=self.options.loop,
3484 3485 runs_per_test=self.options.runs_per_test,
3485 3486 showchannels=self.options.showchannels,
3486 3487 tests=tests,
3487 3488 loadtest=_reloadtest,
3488 3489 )
3489 3490 verbosity = 1
3490 3491 if self.options.list_tests:
3491 3492 verbosity = 0
3492 3493 elif self.options.verbose:
3493 3494 verbosity = 2
3494 3495 runner = TextTestRunner(self, verbosity=verbosity)
3495 3496
3496 3497 osenvironb.pop(b'PYOXIDIZED_IN_MEMORY_RSRC', None)
3497 3498 osenvironb.pop(b'PYOXIDIZED_FILESYSTEM_RSRC', None)
3498 3499
3499 3500 if self.options.list_tests:
3500 3501 result = runner.listtests(suite)
3501 3502 else:
3502 3503 install_start_time = time.monotonic()
3503 3504 self._usecorrectpython()
3504 3505 if self._installdir:
3505 3506 self._installhg()
3506 3507 self._checkhglib("Testing")
3507 3508 if self.options.chg:
3508 3509 assert self._installdir
3509 3510 self._installchg()
3510 3511 if self.options.rhg:
3511 3512 assert self._installdir
3512 3513 self._installrhg()
3513 3514 elif self.options.pyoxidized:
3514 3515 self._build_pyoxidized()
3515 3516 self._use_correct_mercurial()
3516 3517 install_end_time = time.monotonic()
3517 3518 if self._installdir:
3518 3519 msg = 'installed Mercurial in %.2f seconds'
3519 3520 msg %= install_end_time - install_start_time
3520 3521 log(msg)
3521 3522
3522 3523 log(
3523 3524 'running %d tests using %d parallel processes'
3524 3525 % (num_tests, jobs)
3525 3526 )
3526 3527
3527 3528 result = runner.run(suite)
3528 3529
3529 3530 if result.failures or result.errors:
3530 3531 failed = True
3531 3532
3532 3533 result.onEnd()
3533 3534
3534 3535 if self.options.anycoverage:
3535 3536 self._outputcoverage()
3536 3537 except KeyboardInterrupt:
3537 3538 failed = True
3538 3539 print("\ninterrupted!")
3539 3540
3540 3541 if failed:
3541 3542 return 1
3542 3543
3543 3544 def _geterrpath(self, test):
3544 3545 # test['path'] is a relative path
3545 3546 if 'case' in test:
3546 3547 # for multiple dimensions test cases
3547 3548 casestr = b'#'.join(test['case'])
3548 3549 errpath = b'%s#%s.err' % (test['path'], casestr)
3549 3550 else:
3550 3551 errpath = b'%s.err' % test['path']
3551 3552 if self.options.outputdir:
3552 3553 self._outputdir = canonpath(_sys2bytes(self.options.outputdir))
3553 3554 errpath = os.path.join(self._outputdir, errpath)
3554 3555 return errpath
3555 3556
3556 3557 def _getport(self, count):
3557 3558 port = self._ports.get(count) # do we have a cached entry?
3558 3559 if port is None:
3559 3560 portneeded = 3
3560 3561 # above 100 tries we just give up and let test reports failure
3561 3562 for tries in range(100):
3562 3563 allfree = True
3563 3564 port = self.options.port + self._portoffset
3564 3565 for idx in range(portneeded):
3565 3566 if not checkportisavailable(port + idx):
3566 3567 allfree = False
3567 3568 break
3568 3569 self._portoffset += portneeded
3569 3570 if allfree:
3570 3571 break
3571 3572 self._ports[count] = port
3572 3573 return port
3573 3574
3574 3575 def _gettest(self, testdesc, count):
3575 3576 """Obtain a Test by looking at its filename.
3576 3577
3577 3578 Returns a Test instance. The Test may not be runnable if it doesn't
3578 3579 map to a known type.
3579 3580 """
3580 3581 path = testdesc['path']
3581 3582 lctest = path.lower()
3582 3583 testcls = Test
3583 3584
3584 3585 for ext, cls in self.TESTTYPES:
3585 3586 if lctest.endswith(ext):
3586 3587 testcls = cls
3587 3588 break
3588 3589
3589 3590 refpath = os.path.join(getcwdb(), path)
3590 3591 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
3591 3592
3592 3593 # extra keyword parameters. 'case' is used by .t tests
3593 3594 kwds = {k: testdesc[k] for k in ['case'] if k in testdesc}
3594 3595
3595 3596 t = testcls(
3596 3597 refpath,
3597 3598 self._outputdir,
3598 3599 tmpdir,
3599 3600 keeptmpdir=self.options.keep_tmpdir,
3600 3601 debug=self.options.debug,
3601 3602 first=self.options.first,
3602 3603 timeout=self.options.timeout,
3603 3604 startport=self._getport(count),
3604 3605 extraconfigopts=self.options.extra_config_opt,
3605 3606 shell=self.options.shell,
3606 3607 hgcommand=self._hgcommand,
3607 3608 usechg=bool(self.options.with_chg or self.options.chg),
3608 3609 chgdebug=self.options.chg_debug,
3609 3610 useipv6=useipv6,
3610 3611 **kwds
3611 3612 )
3612 3613 t.should_reload = True
3613 3614 return t
3614 3615
3615 3616 def _cleanup(self):
3616 3617 """Clean up state from this test invocation."""
3617 3618 if self.options.keep_tmpdir:
3618 3619 return
3619 3620
3620 3621 vlog("# Cleaning up HGTMP", _bytes2sys(self._hgtmp))
3621 3622 shutil.rmtree(self._hgtmp, True)
3622 3623 for f in self._createdfiles:
3623 3624 try:
3624 3625 os.remove(f)
3625 3626 except OSError:
3626 3627 pass
3627 3628
3628 3629 def _usecorrectpython(self):
3629 3630 """Configure the environment to use the appropriate Python in tests."""
3630 3631 # Tests must use the same interpreter as us or bad things will happen.
3631 3632 if WINDOWS:
3632 3633 pyexe_names = [b'python', b'python3', b'python.exe']
3633 3634 else:
3634 3635 pyexe_names = [b'python', b'python3']
3635 3636
3636 3637 # os.symlink() is a thing with py3 on Windows, but it requires
3637 3638 # Administrator rights.
3638 3639 if not WINDOWS and getattr(os, 'symlink', None):
3639 3640 msg = "# Making python executable in test path a symlink to '%s'"
3640 3641 msg %= sysexecutable
3641 3642 vlog(msg)
3642 3643 for pyexename in pyexe_names:
3643 3644 mypython = os.path.join(self._custom_bin_dir, pyexename)
3644 3645 try:
3645 3646 if os.readlink(mypython) == sysexecutable:
3646 3647 continue
3647 3648 os.unlink(mypython)
3648 3649 except FileNotFoundError:
3649 3650 pass
3650 3651 if self._findprogram(pyexename) != sysexecutable:
3651 3652 try:
3652 3653 os.symlink(sysexecutable, mypython)
3653 3654 self._createdfiles.append(mypython)
3654 3655 except FileExistsError:
3655 3656 # child processes may race, which is harmless
3656 3657 pass
3657 3658 elif WINDOWS and not os.getenv('MSYSTEM'):
3658 3659 raise AssertionError('cannot run test on Windows without MSYSTEM')
3659 3660 else:
3660 3661 # Generate explicit file instead of symlink
3661 3662 #
3662 3663 # This is especially important as Windows doesn't have
3663 3664 # `python3.exe`, and MSYS cannot understand the reparse point with
3664 3665 # that name provided by Microsoft. Create a simple script on PATH
3665 3666 # with that name that delegates to the py3 launcher so the shebang
3666 3667 # lines work.
3667 3668 esc_executable = _sys2bytes(shellquote(sysexecutable))
3668 3669 for pyexename in pyexe_names:
3669 3670 stub_exec_path = os.path.join(self._custom_bin_dir, pyexename)
3670 3671 with open(stub_exec_path, 'wb') as f:
3671 3672 f.write(b'#!/bin/sh\n')
3672 3673 f.write(b'%s "$@"\n' % esc_executable)
3673 3674
3674 3675 if WINDOWS:
3675 3676 # adjust the path to make sur the main python finds it own dll
3676 3677 path = os.environ['PATH'].split(os.pathsep)
3677 3678 main_exec_dir = os.path.dirname(sysexecutable)
3678 3679 extra_paths = [_bytes2sys(self._custom_bin_dir), main_exec_dir]
3679 3680
3680 3681 # Binaries installed by pip into the user area like pylint.exe may
3681 3682 # not be in PATH by default.
3682 3683 appdata = os.environ.get('APPDATA')
3683 3684 vi = sys.version_info
3684 3685 if appdata is not None:
3685 3686 python_dir = 'Python%d%d' % (vi[0], vi[1])
3686 3687 scripts_path = [appdata, 'Python', python_dir, 'Scripts']
3687 3688 scripts_dir = os.path.join(*scripts_path)
3688 3689 extra_paths.append(scripts_dir)
3689 3690
3690 3691 os.environ['PATH'] = os.pathsep.join(extra_paths + path)
3691 3692
3692 3693 def _use_correct_mercurial(self):
3693 3694 target_exec = os.path.join(self._custom_bin_dir, b'hg')
3694 3695 if self._hgcommand != b'hg':
3695 3696 # shutil.which only accept bytes from 3.8
3696 3697 real_exec = which(self._hgcommand)
3697 3698 if real_exec is None:
3698 3699 raise ValueError('could not find exec path for "%s"', real_exec)
3699 3700 if real_exec == target_exec:
3700 3701 # do not overwrite something with itself
3701 3702 return
3702 3703 if WINDOWS:
3703 3704 with open(target_exec, 'wb') as f:
3704 3705 f.write(b'#!/bin/sh\n')
3705 3706 escaped_exec = shellquote(_bytes2sys(real_exec))
3706 3707 f.write(b'%s "$@"\n' % _sys2bytes(escaped_exec))
3707 3708 else:
3708 3709 os.symlink(real_exec, target_exec)
3709 3710 self._createdfiles.append(target_exec)
3710 3711
3711 3712 def _installhg(self):
3712 3713 """Install hg into the test environment.
3713 3714
3714 3715 This will also configure hg with the appropriate testing settings.
3715 3716 """
3716 3717 vlog("# Performing temporary installation of HG")
3717 3718 installerrs = os.path.join(self._hgtmp, b"install.err")
3718 3719 compiler = ''
3719 3720 if self.options.compiler:
3720 3721 compiler = '--compiler ' + self.options.compiler
3721 3722 setup_opts = b""
3722 3723 if self.options.pure:
3723 3724 setup_opts = b"--pure"
3724 3725 elif self.options.rust:
3725 3726 setup_opts = b"--rust"
3726 3727 elif self.options.no_rust:
3727 3728 setup_opts = b"--no-rust"
3728 3729
3729 3730 # Run installer in hg root
3730 3731 compiler = _sys2bytes(compiler)
3731 3732 script = _sys2bytes(os.path.realpath(sys.argv[0]))
3732 3733 exe = _sys2bytes(sysexecutable)
3733 3734 hgroot = os.path.dirname(os.path.dirname(script))
3734 3735 self._hgroot = hgroot
3735 3736 os.chdir(hgroot)
3736 3737 nohome = b'--home=""'
3737 3738 if WINDOWS:
3738 3739 # The --home="" trick works only on OS where os.sep == '/'
3739 3740 # because of a distutils convert_path() fast-path. Avoid it at
3740 3741 # least on Windows for now, deal with .pydistutils.cfg bugs
3741 3742 # when they happen.
3742 3743 nohome = b''
3743 3744 cmd = (
3744 3745 b'"%(exe)s" setup.py %(setup_opts)s clean --all'
3745 3746 b' build %(compiler)s --build-base="%(base)s"'
3746 3747 b' install --force --prefix="%(prefix)s"'
3747 3748 b' --install-lib="%(libdir)s"'
3748 3749 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
3749 3750 % {
3750 3751 b'exe': exe,
3751 3752 b'setup_opts': setup_opts,
3752 3753 b'compiler': compiler,
3753 3754 b'base': os.path.join(self._hgtmp, b"build"),
3754 3755 b'prefix': self._installdir,
3755 3756 b'libdir': self._pythondir,
3756 3757 b'bindir': self._bindir,
3757 3758 b'nohome': nohome,
3758 3759 b'logfile': installerrs,
3759 3760 }
3760 3761 )
3761 3762
3762 3763 # setuptools requires install directories to exist.
3763 3764 def makedirs(p):
3764 3765 try:
3765 3766 os.makedirs(p)
3766 3767 except FileExistsError:
3767 3768 pass
3768 3769
3769 3770 makedirs(self._pythondir)
3770 3771 makedirs(self._bindir)
3771 3772
3772 3773 vlog("# Running", cmd.decode("utf-8"))
3773 if subprocess.call(_bytes2sys(cmd), shell=True) == 0:
3774 if subprocess.call(_bytes2sys(cmd), shell=True, env=original_env) == 0:
3774 3775 if not self.options.verbose:
3775 3776 try:
3776 3777 os.remove(installerrs)
3777 3778 except FileNotFoundError:
3778 3779 pass
3779 3780 else:
3780 3781 with open(installerrs, 'rb') as f:
3781 3782 for line in f:
3782 3783 sys.stdout.buffer.write(line)
3783 3784 sys.exit(1)
3784 3785 os.chdir(self._testdir)
3785 3786
3786 3787 hgbat = os.path.join(self._bindir, b'hg.bat')
3787 3788 if os.path.isfile(hgbat):
3788 3789 # hg.bat expects to be put in bin/scripts while run-tests.py
3789 3790 # installation layout put it in bin/ directly. Fix it
3790 3791 with open(hgbat, 'rb') as f:
3791 3792 data = f.read()
3792 3793 if br'"%~dp0..\python" "%~dp0hg" %*' in data:
3793 3794 data = data.replace(
3794 3795 br'"%~dp0..\python" "%~dp0hg" %*',
3795 3796 b'"%~dp0python" "%~dp0hg" %*',
3796 3797 )
3797 3798 with open(hgbat, 'wb') as f:
3798 3799 f.write(data)
3799 3800 else:
3800 3801 print('WARNING: cannot fix hg.bat reference to python.exe')
3801 3802
3802 3803 if self.options.anycoverage:
3803 3804 custom = os.path.join(
3804 3805 osenvironb[b'RUNTESTDIR'], b'sitecustomize.py'
3805 3806 )
3806 3807 target = os.path.join(self._pythondir, b'sitecustomize.py')
3807 3808 vlog('# Installing coverage trigger to %s' % target)
3808 3809 shutil.copyfile(custom, target)
3809 3810 rc = os.path.join(self._testdir, b'.coveragerc')
3810 3811 vlog('# Installing coverage rc to %s' % rc)
3811 3812 osenvironb[b'COVERAGE_PROCESS_START'] = rc
3812 3813 covdir = os.path.join(self._installdir, b'..', b'coverage')
3813 3814 try:
3814 3815 os.mkdir(covdir)
3815 3816 except FileExistsError:
3816 3817 pass
3817 3818
3818 3819 osenvironb[b'COVERAGE_DIR'] = covdir
3819 3820
3820 3821 def _checkhglib(self, verb):
3821 3822 """Ensure that the 'mercurial' package imported by python is
3822 3823 the one we expect it to be. If not, print a warning to stderr."""
3823 3824 if self._pythondir_inferred:
3824 3825 # The pythondir has been inferred from --with-hg flag.
3825 3826 # We cannot expect anything sensible here.
3826 3827 return
3827 3828 expecthg = os.path.join(self._pythondir, b'mercurial')
3828 3829 actualhg = self._gethgpath()
3829 3830 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
3830 3831 sys.stderr.write(
3831 3832 'warning: %s with unexpected mercurial lib: %s\n'
3832 3833 ' (expected %s)\n' % (verb, actualhg, expecthg)
3833 3834 )
3834 3835
3835 3836 def _gethgpath(self):
3836 3837 """Return the path to the mercurial package that is actually found by
3837 3838 the current Python interpreter."""
3838 3839 if self._hgpath is not None:
3839 3840 return self._hgpath
3840 3841
3841 3842 cmd = b'"%s" -c "import mercurial; print (mercurial.__path__[0])"'
3842 3843 cmd = _bytes2sys(cmd % PYTHON)
3843 3844
3844 3845 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
3845 3846 out, err = p.communicate()
3846 3847
3847 3848 self._hgpath = out.strip()
3848 3849
3849 3850 return self._hgpath
3850 3851
3851 3852 def _installchg(self):
3852 3853 """Install chg into the test environment"""
3853 3854 vlog('# Performing temporary installation of CHG')
3854 3855 assert os.path.dirname(self._bindir) == self._installdir
3855 3856 assert self._hgroot, 'must be called after _installhg()'
3856 3857 cmd = b'"%(make)s" clean install PREFIX="%(prefix)s"' % {
3857 3858 b'make': b'make', # TODO: switch by option or environment?
3858 3859 b'prefix': self._installdir,
3859 3860 }
3860 3861 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
3861 3862 vlog("# Running", cmd)
3862 3863 proc = subprocess.Popen(
3863 3864 cmd,
3864 3865 shell=True,
3865 3866 cwd=cwd,
3866 3867 stdin=subprocess.PIPE,
3867 3868 stdout=subprocess.PIPE,
3868 3869 stderr=subprocess.STDOUT,
3869 3870 )
3870 3871 out, _err = proc.communicate()
3871 3872 if proc.returncode != 0:
3872 3873 sys.stdout.buffer.write(out)
3873 3874 sys.exit(1)
3874 3875
3875 3876 def _installrhg(self):
3876 3877 """Install rhg into the test environment"""
3877 3878 vlog('# Performing temporary installation of rhg')
3878 3879 assert os.path.dirname(self._bindir) == self._installdir
3879 3880 assert self._hgroot, 'must be called after _installhg()'
3880 3881 cmd = b'"%(make)s" install-rhg PREFIX="%(prefix)s"' % {
3881 3882 b'make': b'make', # TODO: switch by option or environment?
3882 3883 b'prefix': self._installdir,
3883 3884 }
3884 3885 cwd = self._hgroot
3885 3886 vlog("# Running", cmd)
3886 3887 proc = subprocess.Popen(
3887 3888 cmd,
3888 3889 shell=True,
3889 3890 cwd=cwd,
3890 3891 stdin=subprocess.PIPE,
3891 3892 stdout=subprocess.PIPE,
3892 3893 stderr=subprocess.STDOUT,
3893 3894 )
3894 3895 out, _err = proc.communicate()
3895 3896 if proc.returncode != 0:
3896 3897 sys.stdout.buffer.write(out)
3897 3898 sys.exit(1)
3898 3899
3899 3900 def _build_pyoxidized(self):
3900 3901 """build a pyoxidized version of mercurial into the test environment
3901 3902
3902 3903 Ideally this function would be `install_pyoxidier` and would both build
3903 3904 and install pyoxidier. However we are starting small to get pyoxidizer
3904 3905 build binary to testing quickly.
3905 3906 """
3906 3907 vlog('# build a pyoxidized version of Mercurial')
3907 3908 assert os.path.dirname(self._bindir) == self._installdir
3908 3909 assert self._hgroot, 'must be called after _installhg()'
3909 3910 target = b''
3910 3911 if WINDOWS:
3911 3912 target = b'windows'
3912 3913 elif MACOS:
3913 3914 target = b'macos'
3914 3915
3915 3916 cmd = b'"%(make)s" pyoxidizer-%(platform)s-tests' % {
3916 3917 b'make': b'make',
3917 3918 b'platform': target,
3918 3919 }
3919 3920 cwd = self._hgroot
3920 3921 vlog("# Running", cmd)
3921 3922 proc = subprocess.Popen(
3922 3923 _bytes2sys(cmd),
3923 3924 shell=True,
3924 3925 cwd=_bytes2sys(cwd),
3925 3926 stdin=subprocess.PIPE,
3926 3927 stdout=subprocess.PIPE,
3927 3928 stderr=subprocess.STDOUT,
3928 3929 )
3929 3930 out, _err = proc.communicate()
3930 3931 if proc.returncode != 0:
3931 3932 sys.stdout.buffer.write(out)
3932 3933 sys.exit(1)
3933 3934
3934 3935 cmd = _bytes2sys(b"%s debuginstall -Tjson" % self._hgcommand)
3935 3936 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
3936 3937 out, err = p.communicate()
3937 3938
3938 3939 props = json.loads(out)[0]
3939 3940
3940 3941 # Affects hghave.py
3941 3942 osenvironb.pop(b'PYOXIDIZED_IN_MEMORY_RSRC', None)
3942 3943 osenvironb.pop(b'PYOXIDIZED_FILESYSTEM_RSRC', None)
3943 3944 if props["hgmodules"] == props["pythonexe"]:
3944 3945 osenvironb[b'PYOXIDIZED_IN_MEMORY_RSRC'] = b'1'
3945 3946 else:
3946 3947 osenvironb[b'PYOXIDIZED_FILESYSTEM_RSRC'] = b'1'
3947 3948
3948 3949 def _outputcoverage(self):
3949 3950 """Produce code coverage output."""
3950 3951 import coverage
3951 3952
3952 3953 coverage = coverage.coverage
3953 3954
3954 3955 vlog('# Producing coverage report')
3955 3956 # chdir is the easiest way to get short, relative paths in the
3956 3957 # output.
3957 3958 os.chdir(self._hgroot)
3958 3959 covdir = os.path.join(_bytes2sys(self._installdir), '..', 'coverage')
3959 3960 cov = coverage(data_file=os.path.join(covdir, 'cov'))
3960 3961
3961 3962 # Map install directory paths back to source directory.
3962 3963 cov.config.paths['srcdir'] = ['.', _bytes2sys(self._pythondir)]
3963 3964
3964 3965 cov.combine()
3965 3966
3966 3967 omit = [
3967 3968 _bytes2sys(os.path.join(x, b'*'))
3968 3969 for x in [self._bindir, self._testdir]
3969 3970 ]
3970 3971 cov.report(ignore_errors=True, omit=omit)
3971 3972
3972 3973 if self.options.htmlcov:
3973 3974 htmldir = os.path.join(_bytes2sys(self._outputdir), 'htmlcov')
3974 3975 cov.html_report(directory=htmldir, omit=omit)
3975 3976 if self.options.annotate:
3976 3977 adir = os.path.join(_bytes2sys(self._outputdir), 'annotated')
3977 3978 if not os.path.isdir(adir):
3978 3979 os.mkdir(adir)
3979 3980 cov.annotate(directory=adir, omit=omit)
3980 3981
3981 3982 def _findprogram(self, program):
3982 3983 """Search PATH for a executable program"""
3983 3984 dpb = _sys2bytes(os.defpath)
3984 3985 sepb = _sys2bytes(os.pathsep)
3985 3986 for p in osenvironb.get(b'PATH', dpb).split(sepb):
3986 3987 name = os.path.join(p, program)
3987 3988 if WINDOWS or os.access(name, os.X_OK):
3988 3989 return _bytes2sys(name)
3989 3990 return None
3990 3991
3991 3992 def _checktools(self):
3992 3993 """Ensure tools required to run tests are present."""
3993 3994 for p in self.REQUIREDTOOLS:
3994 3995 if WINDOWS and not p.endswith(b'.exe'):
3995 3996 p += b'.exe'
3996 3997 found = self._findprogram(p)
3997 3998 p = p.decode("utf-8")
3998 3999 if found:
3999 4000 vlog("# Found prerequisite", p, "at", found)
4000 4001 else:
4001 4002 print("WARNING: Did not find prerequisite tool: %s " % p)
4002 4003
4003 4004
4004 4005 def aggregateexceptions(path):
4005 4006 exceptioncounts = collections.Counter()
4006 4007 testsbyfailure = collections.defaultdict(set)
4007 4008 failuresbytest = collections.defaultdict(set)
4008 4009
4009 4010 for f in os.listdir(path):
4010 4011 with open(os.path.join(path, f), 'rb') as fh:
4011 4012 data = fh.read().split(b'\0')
4012 4013 if len(data) != 5:
4013 4014 continue
4014 4015
4015 4016 exc, mainframe, hgframe, hgline, testname = data
4016 4017 exc = exc.decode('utf-8')
4017 4018 mainframe = mainframe.decode('utf-8')
4018 4019 hgframe = hgframe.decode('utf-8')
4019 4020 hgline = hgline.decode('utf-8')
4020 4021 testname = testname.decode('utf-8')
4021 4022
4022 4023 key = (hgframe, hgline, exc)
4023 4024 exceptioncounts[key] += 1
4024 4025 testsbyfailure[key].add(testname)
4025 4026 failuresbytest[testname].add(key)
4026 4027
4027 4028 # Find test having fewest failures for each failure.
4028 4029 leastfailing = {}
4029 4030 for key, tests in testsbyfailure.items():
4030 4031 fewesttest = None
4031 4032 fewestcount = 99999999
4032 4033 for test in sorted(tests):
4033 4034 if len(failuresbytest[test]) < fewestcount:
4034 4035 fewesttest = test
4035 4036 fewestcount = len(failuresbytest[test])
4036 4037
4037 4038 leastfailing[key] = (fewestcount, fewesttest)
4038 4039
4039 4040 # Create a combined counter so we can sort by total occurrences and
4040 4041 # impacted tests.
4041 4042 combined = {}
4042 4043 for key in exceptioncounts:
4043 4044 combined[key] = (
4044 4045 exceptioncounts[key],
4045 4046 len(testsbyfailure[key]),
4046 4047 leastfailing[key][0],
4047 4048 leastfailing[key][1],
4048 4049 )
4049 4050
4050 4051 return {
4051 4052 'exceptioncounts': exceptioncounts,
4052 4053 'total': sum(exceptioncounts.values()),
4053 4054 'combined': combined,
4054 4055 'leastfailing': leastfailing,
4055 4056 'byfailure': testsbyfailure,
4056 4057 'bytest': failuresbytest,
4057 4058 }
4058 4059
4059 4060
4060 4061 if __name__ == '__main__':
4061 4062 if WINDOWS and not os.getenv('MSYSTEM'):
4062 4063 print('cannot run test on Windows without MSYSTEM', file=sys.stderr)
4063 4064 print(
4064 4065 '(if you need to do so contact the mercurial devs: '
4065 4066 'mercurial@mercurial-scm.org)',
4066 4067 file=sys.stderr,
4067 4068 )
4068 4069 sys.exit(255)
4069 4070
4070 4071 runner = TestRunner()
4071 4072
4072 4073 try:
4073 4074 import msvcrt
4074 4075
4075 4076 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
4076 4077 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
4077 4078 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
4078 4079 except ImportError:
4079 4080 pass
4080 4081
4081 4082 sys.exit(runner.run(sys.argv[1:]))
General Comments 0
You need to be logged in to leave comments. Login now