##// END OF EJS Templates
rhg: add a limited `rhg root` subcommand...
Antoine Cezar -
r45571:3707f6e7 default draft
parent child Browse files
Show More
@@ -0,0 +1,16 b''
1 $ rhg unimplemented-command
2 [252]
3 $ rhg root
4 abort: no repository found in '$TESTTMP' (.hg not found)!
5 [255]
6 $ hg init repository
7 $ cd repository
8 $ rhg root
9 $TESTTMP/repository
10 $ rhg root > /dev/full
11 abort: No space left on device (os error 28)
12 [255]
13 $ rm -rf $PWD
14 $ rhg root
15 abort: error getting current working directory: $ENOENT$
16 [255]
@@ -1,726 +1,727 b''
1 # This file is automatically @generated by Cargo.
1 # This file is automatically @generated by Cargo.
2 # It is not intended for manual editing.
2 # It is not intended for manual editing.
3 [[package]]
3 [[package]]
4 name = "aho-corasick"
4 name = "aho-corasick"
5 version = "0.7.10"
5 version = "0.7.10"
6 source = "registry+https://github.com/rust-lang/crates.io-index"
6 source = "registry+https://github.com/rust-lang/crates.io-index"
7 dependencies = [
7 dependencies = [
8 "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
8 "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
9 ]
9 ]
10
10
11 [[package]]
11 [[package]]
12 name = "ansi_term"
12 name = "ansi_term"
13 version = "0.11.0"
13 version = "0.11.0"
14 source = "registry+https://github.com/rust-lang/crates.io-index"
14 source = "registry+https://github.com/rust-lang/crates.io-index"
15 dependencies = [
15 dependencies = [
16 "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
16 "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
17 ]
17 ]
18
18
19 [[package]]
19 [[package]]
20 name = "atty"
20 name = "atty"
21 version = "0.2.14"
21 version = "0.2.14"
22 source = "registry+https://github.com/rust-lang/crates.io-index"
22 source = "registry+https://github.com/rust-lang/crates.io-index"
23 dependencies = [
23 dependencies = [
24 "hermit-abi 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
24 "hermit-abi 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
25 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
25 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
26 "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
26 "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
27 ]
27 ]
28
28
29 [[package]]
29 [[package]]
30 name = "autocfg"
30 name = "autocfg"
31 version = "1.0.0"
31 version = "1.0.0"
32 source = "registry+https://github.com/rust-lang/crates.io-index"
32 source = "registry+https://github.com/rust-lang/crates.io-index"
33
33
34 [[package]]
34 [[package]]
35 name = "bitflags"
35 name = "bitflags"
36 version = "1.2.1"
36 version = "1.2.1"
37 source = "registry+https://github.com/rust-lang/crates.io-index"
37 source = "registry+https://github.com/rust-lang/crates.io-index"
38
38
39 [[package]]
39 [[package]]
40 name = "byteorder"
40 name = "byteorder"
41 version = "1.3.4"
41 version = "1.3.4"
42 source = "registry+https://github.com/rust-lang/crates.io-index"
42 source = "registry+https://github.com/rust-lang/crates.io-index"
43
43
44 [[package]]
44 [[package]]
45 name = "cfg-if"
45 name = "cfg-if"
46 version = "0.1.10"
46 version = "0.1.10"
47 source = "registry+https://github.com/rust-lang/crates.io-index"
47 source = "registry+https://github.com/rust-lang/crates.io-index"
48
48
49 [[package]]
49 [[package]]
50 name = "chrono"
50 name = "chrono"
51 version = "0.4.11"
51 version = "0.4.11"
52 source = "registry+https://github.com/rust-lang/crates.io-index"
52 source = "registry+https://github.com/rust-lang/crates.io-index"
53 dependencies = [
53 dependencies = [
54 "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
54 "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
55 "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
55 "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
56 "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
56 "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
57 ]
57 ]
58
58
59 [[package]]
59 [[package]]
60 name = "clap"
60 name = "clap"
61 version = "2.33.0"
61 version = "2.33.1"
62 source = "registry+https://github.com/rust-lang/crates.io-index"
62 source = "registry+https://github.com/rust-lang/crates.io-index"
63 dependencies = [
63 dependencies = [
64 "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
64 "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
65 "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
65 "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
66 "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
66 "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
67 "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
67 "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
68 "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
68 "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
69 "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
69 "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
70 "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
70 "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
71 ]
71 ]
72
72
73 [[package]]
73 [[package]]
74 name = "colored"
74 name = "colored"
75 version = "1.9.3"
75 version = "1.9.3"
76 source = "registry+https://github.com/rust-lang/crates.io-index"
76 source = "registry+https://github.com/rust-lang/crates.io-index"
77 dependencies = [
77 dependencies = [
78 "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
78 "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
79 "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
79 "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
80 "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
80 "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
81 ]
81 ]
82
82
83 [[package]]
83 [[package]]
84 name = "cpython"
84 name = "cpython"
85 version = "0.4.1"
85 version = "0.4.1"
86 source = "registry+https://github.com/rust-lang/crates.io-index"
86 source = "registry+https://github.com/rust-lang/crates.io-index"
87 dependencies = [
87 dependencies = [
88 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
88 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
89 "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
89 "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
90 "python27-sys 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
90 "python27-sys 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
91 "python3-sys 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
91 "python3-sys 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
92 ]
92 ]
93
93
94 [[package]]
94 [[package]]
95 name = "crossbeam"
95 name = "crossbeam"
96 version = "0.7.3"
96 version = "0.7.3"
97 source = "registry+https://github.com/rust-lang/crates.io-index"
97 source = "registry+https://github.com/rust-lang/crates.io-index"
98 dependencies = [
98 dependencies = [
99 "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
99 "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
100 "crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
100 "crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
101 "crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
101 "crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
102 "crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
102 "crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
103 "crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
103 "crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
104 "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
104 "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
105 ]
105 ]
106
106
107 [[package]]
107 [[package]]
108 name = "crossbeam-channel"
108 name = "crossbeam-channel"
109 version = "0.4.2"
109 version = "0.4.2"
110 source = "registry+https://github.com/rust-lang/crates.io-index"
110 source = "registry+https://github.com/rust-lang/crates.io-index"
111 dependencies = [
111 dependencies = [
112 "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
112 "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
113 "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
113 "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
114 ]
114 ]
115
115
116 [[package]]
116 [[package]]
117 name = "crossbeam-deque"
117 name = "crossbeam-deque"
118 version = "0.7.3"
118 version = "0.7.3"
119 source = "registry+https://github.com/rust-lang/crates.io-index"
119 source = "registry+https://github.com/rust-lang/crates.io-index"
120 dependencies = [
120 dependencies = [
121 "crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
121 "crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
122 "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
122 "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
123 "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
123 "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
124 ]
124 ]
125
125
126 [[package]]
126 [[package]]
127 name = "crossbeam-epoch"
127 name = "crossbeam-epoch"
128 version = "0.8.2"
128 version = "0.8.2"
129 source = "registry+https://github.com/rust-lang/crates.io-index"
129 source = "registry+https://github.com/rust-lang/crates.io-index"
130 dependencies = [
130 dependencies = [
131 "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
131 "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
132 "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
132 "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
133 "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
133 "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
134 "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
134 "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
135 "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
135 "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
136 "memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
136 "memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
137 "scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
137 "scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
138 ]
138 ]
139
139
140 [[package]]
140 [[package]]
141 name = "crossbeam-queue"
141 name = "crossbeam-queue"
142 version = "0.2.1"
142 version = "0.2.1"
143 source = "registry+https://github.com/rust-lang/crates.io-index"
143 source = "registry+https://github.com/rust-lang/crates.io-index"
144 dependencies = [
144 dependencies = [
145 "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
145 "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
146 "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
146 "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
147 ]
147 ]
148
148
149 [[package]]
149 [[package]]
150 name = "crossbeam-utils"
150 name = "crossbeam-utils"
151 version = "0.7.2"
151 version = "0.7.2"
152 source = "registry+https://github.com/rust-lang/crates.io-index"
152 source = "registry+https://github.com/rust-lang/crates.io-index"
153 dependencies = [
153 dependencies = [
154 "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
154 "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
155 "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
155 "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
156 "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
156 "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
157 ]
157 ]
158
158
159 [[package]]
159 [[package]]
160 name = "ctor"
160 name = "ctor"
161 version = "0.1.13"
161 version = "0.1.13"
162 source = "registry+https://github.com/rust-lang/crates.io-index"
162 source = "registry+https://github.com/rust-lang/crates.io-index"
163 dependencies = [
163 dependencies = [
164 "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
164 "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
165 "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
165 "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
166 ]
166 ]
167
167
168 [[package]]
168 [[package]]
169 name = "difference"
169 name = "difference"
170 version = "2.0.0"
170 version = "2.0.0"
171 source = "registry+https://github.com/rust-lang/crates.io-index"
171 source = "registry+https://github.com/rust-lang/crates.io-index"
172
172
173 [[package]]
173 [[package]]
174 name = "either"
174 name = "either"
175 version = "1.5.3"
175 version = "1.5.3"
176 source = "registry+https://github.com/rust-lang/crates.io-index"
176 source = "registry+https://github.com/rust-lang/crates.io-index"
177
177
178 [[package]]
178 [[package]]
179 name = "getrandom"
179 name = "getrandom"
180 version = "0.1.14"
180 version = "0.1.14"
181 source = "registry+https://github.com/rust-lang/crates.io-index"
181 source = "registry+https://github.com/rust-lang/crates.io-index"
182 dependencies = [
182 dependencies = [
183 "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
183 "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
184 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
184 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
185 "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)",
185 "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)",
186 ]
186 ]
187
187
188 [[package]]
188 [[package]]
189 name = "hermit-abi"
189 name = "hermit-abi"
190 version = "0.1.8"
190 version = "0.1.8"
191 source = "registry+https://github.com/rust-lang/crates.io-index"
191 source = "registry+https://github.com/rust-lang/crates.io-index"
192 dependencies = [
192 dependencies = [
193 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
193 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
194 ]
194 ]
195
195
196 [[package]]
196 [[package]]
197 name = "hex"
197 name = "hex"
198 version = "0.4.2"
198 version = "0.4.2"
199 source = "registry+https://github.com/rust-lang/crates.io-index"
199 source = "registry+https://github.com/rust-lang/crates.io-index"
200
200
201 [[package]]
201 [[package]]
202 name = "hg-core"
202 name = "hg-core"
203 version = "0.1.0"
203 version = "0.1.0"
204 dependencies = [
204 dependencies = [
205 "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
205 "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
206 "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
206 "clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)",
207 "crossbeam 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
207 "crossbeam 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
208 "hex 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
208 "hex 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
209 "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
209 "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
210 "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
210 "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
211 "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
211 "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
212 "memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
212 "memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
213 "micro-timer 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
213 "micro-timer 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
214 "pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
214 "pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
215 "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
215 "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
216 "rand_distr 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
216 "rand_distr 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
217 "rand_pcg 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
217 "rand_pcg 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
218 "rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
218 "rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
219 "regex 1.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
219 "regex 1.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
220 "same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
220 "same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
221 "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
221 "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
222 "twox-hash 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
222 "twox-hash 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
223 ]
223 ]
224
224
225 [[package]]
225 [[package]]
226 name = "hg-cpython"
226 name = "hg-cpython"
227 version = "0.1.0"
227 version = "0.1.0"
228 dependencies = [
228 dependencies = [
229 "cpython 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
229 "cpython 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
230 "hg-core 0.1.0",
230 "hg-core 0.1.0",
231 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
231 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
232 "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
232 "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
233 "simple_logger 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
233 "simple_logger 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
234 ]
234 ]
235
235
236 [[package]]
236 [[package]]
237 name = "lazy_static"
237 name = "lazy_static"
238 version = "1.4.0"
238 version = "1.4.0"
239 source = "registry+https://github.com/rust-lang/crates.io-index"
239 source = "registry+https://github.com/rust-lang/crates.io-index"
240
240
241 [[package]]
241 [[package]]
242 name = "libc"
242 name = "libc"
243 version = "0.2.67"
243 version = "0.2.67"
244 source = "registry+https://github.com/rust-lang/crates.io-index"
244 source = "registry+https://github.com/rust-lang/crates.io-index"
245
245
246 [[package]]
246 [[package]]
247 name = "log"
247 name = "log"
248 version = "0.4.8"
248 version = "0.4.8"
249 source = "registry+https://github.com/rust-lang/crates.io-index"
249 source = "registry+https://github.com/rust-lang/crates.io-index"
250 dependencies = [
250 dependencies = [
251 "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
251 "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
252 ]
252 ]
253
253
254 [[package]]
254 [[package]]
255 name = "maybe-uninit"
255 name = "maybe-uninit"
256 version = "2.0.0"
256 version = "2.0.0"
257 source = "registry+https://github.com/rust-lang/crates.io-index"
257 source = "registry+https://github.com/rust-lang/crates.io-index"
258
258
259 [[package]]
259 [[package]]
260 name = "memchr"
260 name = "memchr"
261 version = "2.3.3"
261 version = "2.3.3"
262 source = "registry+https://github.com/rust-lang/crates.io-index"
262 source = "registry+https://github.com/rust-lang/crates.io-index"
263
263
264 [[package]]
264 [[package]]
265 name = "memmap"
265 name = "memmap"
266 version = "0.7.0"
266 version = "0.7.0"
267 source = "registry+https://github.com/rust-lang/crates.io-index"
267 source = "registry+https://github.com/rust-lang/crates.io-index"
268 dependencies = [
268 dependencies = [
269 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
269 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
270 "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
270 "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
271 ]
271 ]
272
272
273 [[package]]
273 [[package]]
274 name = "memoffset"
274 name = "memoffset"
275 version = "0.5.3"
275 version = "0.5.3"
276 source = "registry+https://github.com/rust-lang/crates.io-index"
276 source = "registry+https://github.com/rust-lang/crates.io-index"
277 dependencies = [
277 dependencies = [
278 "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
278 "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
279 ]
279 ]
280
280
281 [[package]]
281 [[package]]
282 name = "micro-timer"
282 name = "micro-timer"
283 version = "0.3.0"
283 version = "0.3.0"
284 source = "registry+https://github.com/rust-lang/crates.io-index"
284 source = "registry+https://github.com/rust-lang/crates.io-index"
285 dependencies = [
285 dependencies = [
286 "micro-timer-macros 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
286 "micro-timer-macros 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
287 "scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
287 "scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
288 ]
288 ]
289
289
290 [[package]]
290 [[package]]
291 name = "micro-timer-macros"
291 name = "micro-timer-macros"
292 version = "0.3.0"
292 version = "0.3.0"
293 source = "registry+https://github.com/rust-lang/crates.io-index"
293 source = "registry+https://github.com/rust-lang/crates.io-index"
294 dependencies = [
294 dependencies = [
295 "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
295 "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
296 "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
296 "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
297 "scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
297 "scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
298 "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
298 "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
299 ]
299 ]
300
300
301 [[package]]
301 [[package]]
302 name = "num-integer"
302 name = "num-integer"
303 version = "0.1.42"
303 version = "0.1.42"
304 source = "registry+https://github.com/rust-lang/crates.io-index"
304 source = "registry+https://github.com/rust-lang/crates.io-index"
305 dependencies = [
305 dependencies = [
306 "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
306 "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
307 "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
307 "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
308 ]
308 ]
309
309
310 [[package]]
310 [[package]]
311 name = "num-traits"
311 name = "num-traits"
312 version = "0.2.11"
312 version = "0.2.11"
313 source = "registry+https://github.com/rust-lang/crates.io-index"
313 source = "registry+https://github.com/rust-lang/crates.io-index"
314 dependencies = [
314 dependencies = [
315 "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
315 "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
316 ]
316 ]
317
317
318 [[package]]
318 [[package]]
319 name = "num_cpus"
319 name = "num_cpus"
320 version = "1.12.0"
320 version = "1.12.0"
321 source = "registry+https://github.com/rust-lang/crates.io-index"
321 source = "registry+https://github.com/rust-lang/crates.io-index"
322 dependencies = [
322 dependencies = [
323 "hermit-abi 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
323 "hermit-abi 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
324 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
324 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
325 ]
325 ]
326
326
327 [[package]]
327 [[package]]
328 name = "output_vt100"
328 name = "output_vt100"
329 version = "0.1.2"
329 version = "0.1.2"
330 source = "registry+https://github.com/rust-lang/crates.io-index"
330 source = "registry+https://github.com/rust-lang/crates.io-index"
331 dependencies = [
331 dependencies = [
332 "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
332 "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
333 ]
333 ]
334
334
335 [[package]]
335 [[package]]
336 name = "ppv-lite86"
336 name = "ppv-lite86"
337 version = "0.2.6"
337 version = "0.2.6"
338 source = "registry+https://github.com/rust-lang/crates.io-index"
338 source = "registry+https://github.com/rust-lang/crates.io-index"
339
339
340 [[package]]
340 [[package]]
341 name = "pretty_assertions"
341 name = "pretty_assertions"
342 version = "0.6.1"
342 version = "0.6.1"
343 source = "registry+https://github.com/rust-lang/crates.io-index"
343 source = "registry+https://github.com/rust-lang/crates.io-index"
344 dependencies = [
344 dependencies = [
345 "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
345 "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
346 "ctor 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
346 "ctor 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
347 "difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
347 "difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
348 "output_vt100 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
348 "output_vt100 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
349 ]
349 ]
350
350
351 [[package]]
351 [[package]]
352 name = "proc-macro2"
352 name = "proc-macro2"
353 version = "1.0.9"
353 version = "1.0.9"
354 source = "registry+https://github.com/rust-lang/crates.io-index"
354 source = "registry+https://github.com/rust-lang/crates.io-index"
355 dependencies = [
355 dependencies = [
356 "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
356 "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
357 ]
357 ]
358
358
359 [[package]]
359 [[package]]
360 name = "python27-sys"
360 name = "python27-sys"
361 version = "0.4.1"
361 version = "0.4.1"
362 source = "registry+https://github.com/rust-lang/crates.io-index"
362 source = "registry+https://github.com/rust-lang/crates.io-index"
363 dependencies = [
363 dependencies = [
364 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
364 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
365 "regex 1.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
365 "regex 1.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
366 ]
366 ]
367
367
368 [[package]]
368 [[package]]
369 name = "python3-sys"
369 name = "python3-sys"
370 version = "0.4.1"
370 version = "0.4.1"
371 source = "registry+https://github.com/rust-lang/crates.io-index"
371 source = "registry+https://github.com/rust-lang/crates.io-index"
372 dependencies = [
372 dependencies = [
373 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
373 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
374 "regex 1.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
374 "regex 1.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
375 ]
375 ]
376
376
377 [[package]]
377 [[package]]
378 name = "quote"
378 name = "quote"
379 version = "1.0.3"
379 version = "1.0.3"
380 source = "registry+https://github.com/rust-lang/crates.io-index"
380 source = "registry+https://github.com/rust-lang/crates.io-index"
381 dependencies = [
381 dependencies = [
382 "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
382 "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
383 ]
383 ]
384
384
385 [[package]]
385 [[package]]
386 name = "rand"
386 name = "rand"
387 version = "0.7.3"
387 version = "0.7.3"
388 source = "registry+https://github.com/rust-lang/crates.io-index"
388 source = "registry+https://github.com/rust-lang/crates.io-index"
389 dependencies = [
389 dependencies = [
390 "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
390 "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
391 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
391 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
392 "rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
392 "rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
393 "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
393 "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
394 "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
394 "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
395 ]
395 ]
396
396
397 [[package]]
397 [[package]]
398 name = "rand_chacha"
398 name = "rand_chacha"
399 version = "0.2.2"
399 version = "0.2.2"
400 source = "registry+https://github.com/rust-lang/crates.io-index"
400 source = "registry+https://github.com/rust-lang/crates.io-index"
401 dependencies = [
401 dependencies = [
402 "ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
402 "ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
403 "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
403 "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
404 ]
404 ]
405
405
406 [[package]]
406 [[package]]
407 name = "rand_core"
407 name = "rand_core"
408 version = "0.5.1"
408 version = "0.5.1"
409 source = "registry+https://github.com/rust-lang/crates.io-index"
409 source = "registry+https://github.com/rust-lang/crates.io-index"
410 dependencies = [
410 dependencies = [
411 "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
411 "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
412 ]
412 ]
413
413
414 [[package]]
414 [[package]]
415 name = "rand_distr"
415 name = "rand_distr"
416 version = "0.2.2"
416 version = "0.2.2"
417 source = "registry+https://github.com/rust-lang/crates.io-index"
417 source = "registry+https://github.com/rust-lang/crates.io-index"
418 dependencies = [
418 dependencies = [
419 "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
419 "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
420 ]
420 ]
421
421
422 [[package]]
422 [[package]]
423 name = "rand_hc"
423 name = "rand_hc"
424 version = "0.2.0"
424 version = "0.2.0"
425 source = "registry+https://github.com/rust-lang/crates.io-index"
425 source = "registry+https://github.com/rust-lang/crates.io-index"
426 dependencies = [
426 dependencies = [
427 "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
427 "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
428 ]
428 ]
429
429
430 [[package]]
430 [[package]]
431 name = "rand_pcg"
431 name = "rand_pcg"
432 version = "0.2.1"
432 version = "0.2.1"
433 source = "registry+https://github.com/rust-lang/crates.io-index"
433 source = "registry+https://github.com/rust-lang/crates.io-index"
434 dependencies = [
434 dependencies = [
435 "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
435 "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
436 ]
436 ]
437
437
438 [[package]]
438 [[package]]
439 name = "rayon"
439 name = "rayon"
440 version = "1.3.0"
440 version = "1.3.0"
441 source = "registry+https://github.com/rust-lang/crates.io-index"
441 source = "registry+https://github.com/rust-lang/crates.io-index"
442 dependencies = [
442 dependencies = [
443 "crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
443 "crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
444 "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
444 "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
445 "rayon-core 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
445 "rayon-core 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
446 ]
446 ]
447
447
448 [[package]]
448 [[package]]
449 name = "rayon-core"
449 name = "rayon-core"
450 version = "1.7.0"
450 version = "1.7.0"
451 source = "registry+https://github.com/rust-lang/crates.io-index"
451 source = "registry+https://github.com/rust-lang/crates.io-index"
452 dependencies = [
452 dependencies = [
453 "crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
453 "crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
454 "crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
454 "crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
455 "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
455 "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
456 "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
456 "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
457 "num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
457 "num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
458 ]
458 ]
459
459
460 [[package]]
460 [[package]]
461 name = "redox_syscall"
461 name = "redox_syscall"
462 version = "0.1.56"
462 version = "0.1.56"
463 source = "registry+https://github.com/rust-lang/crates.io-index"
463 source = "registry+https://github.com/rust-lang/crates.io-index"
464
464
465 [[package]]
465 [[package]]
466 name = "regex"
466 name = "regex"
467 version = "1.3.9"
467 version = "1.3.9"
468 source = "registry+https://github.com/rust-lang/crates.io-index"
468 source = "registry+https://github.com/rust-lang/crates.io-index"
469 dependencies = [
469 dependencies = [
470 "aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)",
470 "aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)",
471 "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
471 "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
472 "regex-syntax 0.6.18 (registry+https://github.com/rust-lang/crates.io-index)",
472 "regex-syntax 0.6.18 (registry+https://github.com/rust-lang/crates.io-index)",
473 "thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
473 "thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
474 ]
474 ]
475
475
476 [[package]]
476 [[package]]
477 name = "regex-syntax"
477 name = "regex-syntax"
478 version = "0.6.18"
478 version = "0.6.18"
479 source = "registry+https://github.com/rust-lang/crates.io-index"
479 source = "registry+https://github.com/rust-lang/crates.io-index"
480
480
481 [[package]]
481 [[package]]
482 name = "remove_dir_all"
482 name = "remove_dir_all"
483 version = "0.5.2"
483 version = "0.5.2"
484 source = "registry+https://github.com/rust-lang/crates.io-index"
484 source = "registry+https://github.com/rust-lang/crates.io-index"
485 dependencies = [
485 dependencies = [
486 "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
486 "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
487 ]
487 ]
488
488
489 [[package]]
489 [[package]]
490 name = "rhg"
490 name = "rhg"
491 version = "0.1.0"
491 version = "0.1.0"
492 dependencies = [
492 dependencies = [
493 "clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)",
493 "hg-core 0.1.0",
494 "hg-core 0.1.0",
494 ]
495 ]
495
496
496 [[package]]
497 [[package]]
497 name = "rustc_version"
498 name = "rustc_version"
498 version = "0.2.3"
499 version = "0.2.3"
499 source = "registry+https://github.com/rust-lang/crates.io-index"
500 source = "registry+https://github.com/rust-lang/crates.io-index"
500 dependencies = [
501 dependencies = [
501 "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
502 "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
502 ]
503 ]
503
504
504 [[package]]
505 [[package]]
505 name = "same-file"
506 name = "same-file"
506 version = "1.0.6"
507 version = "1.0.6"
507 source = "registry+https://github.com/rust-lang/crates.io-index"
508 source = "registry+https://github.com/rust-lang/crates.io-index"
508 dependencies = [
509 dependencies = [
509 "winapi-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
510 "winapi-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
510 ]
511 ]
511
512
512 [[package]]
513 [[package]]
513 name = "scopeguard"
514 name = "scopeguard"
514 version = "1.1.0"
515 version = "1.1.0"
515 source = "registry+https://github.com/rust-lang/crates.io-index"
516 source = "registry+https://github.com/rust-lang/crates.io-index"
516
517
517 [[package]]
518 [[package]]
518 name = "semver"
519 name = "semver"
519 version = "0.9.0"
520 version = "0.9.0"
520 source = "registry+https://github.com/rust-lang/crates.io-index"
521 source = "registry+https://github.com/rust-lang/crates.io-index"
521 dependencies = [
522 dependencies = [
522 "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
523 "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
523 ]
524 ]
524
525
525 [[package]]
526 [[package]]
526 name = "semver-parser"
527 name = "semver-parser"
527 version = "0.7.0"
528 version = "0.7.0"
528 source = "registry+https://github.com/rust-lang/crates.io-index"
529 source = "registry+https://github.com/rust-lang/crates.io-index"
529
530
530 [[package]]
531 [[package]]
531 name = "simple_logger"
532 name = "simple_logger"
532 version = "1.6.0"
533 version = "1.6.0"
533 source = "registry+https://github.com/rust-lang/crates.io-index"
534 source = "registry+https://github.com/rust-lang/crates.io-index"
534 dependencies = [
535 dependencies = [
535 "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
536 "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
536 "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
537 "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
537 "colored 1.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
538 "colored 1.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
538 "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
539 "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
539 "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
540 "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
540 ]
541 ]
541
542
542 [[package]]
543 [[package]]
543 name = "strsim"
544 name = "strsim"
544 version = "0.8.0"
545 version = "0.8.0"
545 source = "registry+https://github.com/rust-lang/crates.io-index"
546 source = "registry+https://github.com/rust-lang/crates.io-index"
546
547
547 [[package]]
548 [[package]]
548 name = "syn"
549 name = "syn"
549 version = "1.0.16"
550 version = "1.0.16"
550 source = "registry+https://github.com/rust-lang/crates.io-index"
551 source = "registry+https://github.com/rust-lang/crates.io-index"
551 dependencies = [
552 dependencies = [
552 "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
553 "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
553 "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
554 "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
554 "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
555 "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
555 ]
556 ]
556
557
557 [[package]]
558 [[package]]
558 name = "tempfile"
559 name = "tempfile"
559 version = "3.1.0"
560 version = "3.1.0"
560 source = "registry+https://github.com/rust-lang/crates.io-index"
561 source = "registry+https://github.com/rust-lang/crates.io-index"
561 dependencies = [
562 dependencies = [
562 "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
563 "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
563 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
564 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
564 "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
565 "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
565 "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
566 "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
566 "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
567 "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
567 "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
568 "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
568 ]
569 ]
569
570
570 [[package]]
571 [[package]]
571 name = "textwrap"
572 name = "textwrap"
572 version = "0.11.0"
573 version = "0.11.0"
573 source = "registry+https://github.com/rust-lang/crates.io-index"
574 source = "registry+https://github.com/rust-lang/crates.io-index"
574 dependencies = [
575 dependencies = [
575 "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
576 "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
576 ]
577 ]
577
578
578 [[package]]
579 [[package]]
579 name = "thread_local"
580 name = "thread_local"
580 version = "1.0.1"
581 version = "1.0.1"
581 source = "registry+https://github.com/rust-lang/crates.io-index"
582 source = "registry+https://github.com/rust-lang/crates.io-index"
582 dependencies = [
583 dependencies = [
583 "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
584 "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
584 ]
585 ]
585
586
586 [[package]]
587 [[package]]
587 name = "time"
588 name = "time"
588 version = "0.1.42"
589 version = "0.1.42"
589 source = "registry+https://github.com/rust-lang/crates.io-index"
590 source = "registry+https://github.com/rust-lang/crates.io-index"
590 dependencies = [
591 dependencies = [
591 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
592 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
592 "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
593 "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
593 "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
594 "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
594 ]
595 ]
595
596
596 [[package]]
597 [[package]]
597 name = "twox-hash"
598 name = "twox-hash"
598 version = "1.5.0"
599 version = "1.5.0"
599 source = "registry+https://github.com/rust-lang/crates.io-index"
600 source = "registry+https://github.com/rust-lang/crates.io-index"
600 dependencies = [
601 dependencies = [
601 "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
602 "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
602 ]
603 ]
603
604
604 [[package]]
605 [[package]]
605 name = "unicode-width"
606 name = "unicode-width"
606 version = "0.1.7"
607 version = "0.1.7"
607 source = "registry+https://github.com/rust-lang/crates.io-index"
608 source = "registry+https://github.com/rust-lang/crates.io-index"
608
609
609 [[package]]
610 [[package]]
610 name = "unicode-xid"
611 name = "unicode-xid"
611 version = "0.2.0"
612 version = "0.2.0"
612 source = "registry+https://github.com/rust-lang/crates.io-index"
613 source = "registry+https://github.com/rust-lang/crates.io-index"
613
614
614 [[package]]
615 [[package]]
615 name = "vec_map"
616 name = "vec_map"
616 version = "0.8.1"
617 version = "0.8.1"
617 source = "registry+https://github.com/rust-lang/crates.io-index"
618 source = "registry+https://github.com/rust-lang/crates.io-index"
618
619
619 [[package]]
620 [[package]]
620 name = "wasi"
621 name = "wasi"
621 version = "0.9.0+wasi-snapshot-preview1"
622 version = "0.9.0+wasi-snapshot-preview1"
622 source = "registry+https://github.com/rust-lang/crates.io-index"
623 source = "registry+https://github.com/rust-lang/crates.io-index"
623
624
624 [[package]]
625 [[package]]
625 name = "winapi"
626 name = "winapi"
626 version = "0.3.8"
627 version = "0.3.8"
627 source = "registry+https://github.com/rust-lang/crates.io-index"
628 source = "registry+https://github.com/rust-lang/crates.io-index"
628 dependencies = [
629 dependencies = [
629 "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
630 "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
630 "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
631 "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
631 ]
632 ]
632
633
633 [[package]]
634 [[package]]
634 name = "winapi-i686-pc-windows-gnu"
635 name = "winapi-i686-pc-windows-gnu"
635 version = "0.4.0"
636 version = "0.4.0"
636 source = "registry+https://github.com/rust-lang/crates.io-index"
637 source = "registry+https://github.com/rust-lang/crates.io-index"
637
638
638 [[package]]
639 [[package]]
639 name = "winapi-util"
640 name = "winapi-util"
640 version = "0.1.3"
641 version = "0.1.3"
641 source = "registry+https://github.com/rust-lang/crates.io-index"
642 source = "registry+https://github.com/rust-lang/crates.io-index"
642 dependencies = [
643 dependencies = [
643 "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
644 "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
644 ]
645 ]
645
646
646 [[package]]
647 [[package]]
647 name = "winapi-x86_64-pc-windows-gnu"
648 name = "winapi-x86_64-pc-windows-gnu"
648 version = "0.4.0"
649 version = "0.4.0"
649 source = "registry+https://github.com/rust-lang/crates.io-index"
650 source = "registry+https://github.com/rust-lang/crates.io-index"
650
651
651 [metadata]
652 [metadata]
652 "checksum aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)" = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada"
653 "checksum aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)" = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada"
653 "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
654 "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
654 "checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
655 "checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
655 "checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
656 "checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
656 "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
657 "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
657 "checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
658 "checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
658 "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
659 "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
659 "checksum chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2"
660 "checksum chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2"
660 "checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
661 "checksum clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129"
661 "checksum colored 1.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f4ffc801dacf156c5854b9df4f425a626539c3a6ef7893cc0c5084a23f0b6c59"
662 "checksum colored 1.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f4ffc801dacf156c5854b9df4f425a626539c3a6ef7893cc0c5084a23f0b6c59"
662 "checksum cpython 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bfaf3847ab963e40c4f6dd8d6be279bdf74007ae2413786a0dcbb28c52139a95"
663 "checksum cpython 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bfaf3847ab963e40c4f6dd8d6be279bdf74007ae2413786a0dcbb28c52139a95"
663 "checksum crossbeam 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "69323bff1fb41c635347b8ead484a5ca6c3f11914d784170b158d8449ab07f8e"
664 "checksum crossbeam 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "69323bff1fb41c635347b8ead484a5ca6c3f11914d784170b158d8449ab07f8e"
664 "checksum crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061"
665 "checksum crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061"
665 "checksum crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285"
666 "checksum crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285"
666 "checksum crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace"
667 "checksum crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace"
667 "checksum crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db"
668 "checksum crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db"
668 "checksum crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
669 "checksum crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
669 "checksum ctor 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "47c5e5ac752e18207b12e16b10631ae5f7f68f8805f335f9b817ead83d9ffce1"
670 "checksum ctor 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "47c5e5ac752e18207b12e16b10631ae5f7f68f8805f335f9b817ead83d9ffce1"
670 "checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
671 "checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
671 "checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
672 "checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
672 "checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
673 "checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
673 "checksum hermit-abi 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1010591b26bbfe835e9faeabeb11866061cc7dcebffd56ad7d0942d0e61aefd8"
674 "checksum hermit-abi 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1010591b26bbfe835e9faeabeb11866061cc7dcebffd56ad7d0942d0e61aefd8"
674 "checksum hex 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35"
675 "checksum hex 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35"
675 "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
676 "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
676 "checksum libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)" = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018"
677 "checksum libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)" = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018"
677 "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
678 "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
678 "checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
679 "checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
679 "checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
680 "checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
680 "checksum memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b"
681 "checksum memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b"
681 "checksum memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "75189eb85871ea5c2e2c15abbdd541185f63b408415e5051f5cac122d8c774b9"
682 "checksum memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "75189eb85871ea5c2e2c15abbdd541185f63b408415e5051f5cac122d8c774b9"
682 "checksum micro-timer 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "25b31d6cb9112984323d05d7a353f272ae5d7a307074f9ab9b25c00121b8c947"
683 "checksum micro-timer 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "25b31d6cb9112984323d05d7a353f272ae5d7a307074f9ab9b25c00121b8c947"
683 "checksum micro-timer-macros 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5694085dd384bb9e824207facc040c248d9df653f55e28c3ad0686958b448504"
684 "checksum micro-timer-macros 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5694085dd384bb9e824207facc040c248d9df653f55e28c3ad0686958b448504"
684 "checksum num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba"
685 "checksum num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba"
685 "checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096"
686 "checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096"
686 "checksum num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6"
687 "checksum num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6"
687 "checksum output_vt100 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9"
688 "checksum output_vt100 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9"
688 "checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
689 "checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
689 "checksum pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427"
690 "checksum pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427"
690 "checksum proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435"
691 "checksum proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435"
691 "checksum python27-sys 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "67cb041de8615111bf224dd75667af5f25c6e032118251426fed7f1b70ce4c8c"
692 "checksum python27-sys 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "67cb041de8615111bf224dd75667af5f25c6e032118251426fed7f1b70ce4c8c"
692 "checksum python3-sys 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90af11779515a1e530af60782d273b59ac79d33b0e253c071a728563957c76d4"
693 "checksum python3-sys 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90af11779515a1e530af60782d273b59ac79d33b0e253c071a728563957c76d4"
693 "checksum quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f"
694 "checksum quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f"
694 "checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
695 "checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
695 "checksum rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
696 "checksum rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
696 "checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
697 "checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
697 "checksum rand_distr 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "96977acbdd3a6576fb1d27391900035bf3863d4a16422973a409b488cf29ffb2"
698 "checksum rand_distr 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "96977acbdd3a6576fb1d27391900035bf3863d4a16422973a409b488cf29ffb2"
698 "checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
699 "checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
699 "checksum rand_pcg 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
700 "checksum rand_pcg 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
700 "checksum rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "db6ce3297f9c85e16621bb8cca38a06779ffc31bb8184e1be4bed2be4678a098"
701 "checksum rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "db6ce3297f9c85e16621bb8cca38a06779ffc31bb8184e1be4bed2be4678a098"
701 "checksum rayon-core 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "08a89b46efaf957e52b18062fb2f4660f8b8a4dde1807ca002690868ef2c85a9"
702 "checksum rayon-core 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "08a89b46efaf957e52b18062fb2f4660f8b8a4dde1807ca002690868ef2c85a9"
702 "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
703 "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
703 "checksum regex 1.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6"
704 "checksum regex 1.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6"
704 "checksum regex-syntax 0.6.18 (registry+https://github.com/rust-lang/crates.io-index)" = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8"
705 "checksum regex-syntax 0.6.18 (registry+https://github.com/rust-lang/crates.io-index)" = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8"
705 "checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
706 "checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
706 "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
707 "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
707 "checksum same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
708 "checksum same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
708 "checksum scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
709 "checksum scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
709 "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
710 "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
710 "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
711 "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
711 "checksum simple_logger 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fea0c4611f32f4c2bac73754f22dca1f57e6c1945e0590dae4e5f2a077b92367"
712 "checksum simple_logger 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fea0c4611f32f4c2bac73754f22dca1f57e6c1945e0590dae4e5f2a077b92367"
712 "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
713 "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
713 "checksum syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)" = "123bd9499cfb380418d509322d7a6d52e5315f064fe4b3ad18a53d6b92c07859"
714 "checksum syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)" = "123bd9499cfb380418d509322d7a6d52e5315f064fe4b3ad18a53d6b92c07859"
714 "checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
715 "checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
715 "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
716 "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
716 "checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
717 "checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
717 "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
718 "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
718 "checksum twox-hash 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3bfd5b7557925ce778ff9b9ef90e3ade34c524b5ff10e239c69a42d546d2af56"
719 "checksum twox-hash 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3bfd5b7557925ce778ff9b9ef90e3ade34c524b5ff10e239c69a42d546d2af56"
719 "checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
720 "checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
720 "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
721 "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
721 "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
722 "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
722 "checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
723 "checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
723 "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
724 "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
724 "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
725 "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
725 "checksum winapi-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4ccfbf554c6ad11084fb7517daca16cfdcaccbdadba4fc336f032a8b12c2ad80"
726 "checksum winapi-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4ccfbf554c6ad11084fb7517daca16cfdcaccbdadba4fc336f032a8b12c2ad80"
726 "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
727 "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
@@ -1,9 +1,10 b''
1 [package]
1 [package]
2 name = "rhg"
2 name = "rhg"
3 version = "0.1.0"
3 version = "0.1.0"
4 authors = ["Antoine Cezar <antoine.cezar@octobus.net>"]
4 authors = ["Antoine Cezar <antoine.cezar@octobus.net>"]
5 edition = "2018"
5 edition = "2018"
6
6
7 [dependencies]
7 [dependencies]
8 hg-core = { path = "../hg-core"}
8 hg-core = { path = "../hg-core"}
9 clap = "2.33.1"
9
10
@@ -1,8 +1,42 b''
1 use clap::App;
2 use clap::AppSettings;
3 use clap::SubCommand;
4
1 mod commands;
5 mod commands;
2 mod error;
6 mod error;
3 mod exitcode;
7 mod exitcode;
4 mod ui;
8 mod ui;
9 use commands::Command;
5
10
6 fn main() {
11 fn main() {
7 std::process::exit(exitcode::UNIMPLEMENTED_COMMAND)
12 let mut app = App::new("rhg")
13 .setting(AppSettings::AllowInvalidUtf8)
14 .setting(AppSettings::SubcommandRequired)
15 .setting(AppSettings::VersionlessSubcommands)
16 .version("0.0.1")
17 .subcommand(
18 SubCommand::with_name("root").about(commands::root::HELP_TEXT),
19 );
20
21 let matches = app.clone().get_matches_safe().unwrap_or_else(|_| {
22 std::process::exit(exitcode::UNIMPLEMENTED_COMMAND)
23 });
24
25 let command_result = match matches.subcommand_name() {
26 Some(name) => match name {
27 "root" => commands::root::RootCommand::new().run(),
28 _ => std::process::exit(exitcode::UNIMPLEMENTED_COMMAND),
29 },
30 _ => {
31 match app.print_help() {
32 Ok(_) => std::process::exit(exitcode::OK),
33 Err(_) => std::process::exit(exitcode::ABORT),
34 };
35 }
36 };
37
38 match command_result {
39 Ok(_) => std::process::exit(exitcode::OK),
40 Err(e) => e.exit(),
41 }
8 }
42 }
@@ -1,3768 +1,3775 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # run-tests.py - Run a set of tests on Mercurial
3 # run-tests.py - Run a set of tests on Mercurial
4 #
4 #
5 # Copyright 2006 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Matt Mackall <mpm@selenic.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 # Modifying this script is tricky because it has many modes:
10 # Modifying this script is tricky because it has many modes:
11 # - serial (default) vs parallel (-jN, N > 1)
11 # - serial (default) vs parallel (-jN, N > 1)
12 # - no coverage (default) vs coverage (-c, -C, -s)
12 # - no coverage (default) vs coverage (-c, -C, -s)
13 # - temp install (default) vs specific hg script (--with-hg, --local)
13 # - temp install (default) vs specific hg script (--with-hg, --local)
14 # - tests are a mix of shell scripts and Python scripts
14 # - tests are a mix of shell scripts and Python scripts
15 #
15 #
16 # If you change this script, it is recommended that you ensure you
16 # If you change this script, it is recommended that you ensure you
17 # haven't broken it by running it in various modes with a representative
17 # haven't broken it by running it in various modes with a representative
18 # sample of test scripts. For example:
18 # sample of test scripts. For example:
19 #
19 #
20 # 1) serial, no coverage, temp install:
20 # 1) serial, no coverage, temp install:
21 # ./run-tests.py test-s*
21 # ./run-tests.py test-s*
22 # 2) serial, no coverage, local hg:
22 # 2) serial, no coverage, local hg:
23 # ./run-tests.py --local test-s*
23 # ./run-tests.py --local test-s*
24 # 3) serial, coverage, temp install:
24 # 3) serial, coverage, temp install:
25 # ./run-tests.py -c test-s*
25 # ./run-tests.py -c test-s*
26 # 4) serial, coverage, local hg:
26 # 4) serial, coverage, local hg:
27 # ./run-tests.py -c --local test-s* # unsupported
27 # ./run-tests.py -c --local test-s* # unsupported
28 # 5) parallel, no coverage, temp install:
28 # 5) parallel, no coverage, temp install:
29 # ./run-tests.py -j2 test-s*
29 # ./run-tests.py -j2 test-s*
30 # 6) parallel, no coverage, local hg:
30 # 6) parallel, no coverage, local hg:
31 # ./run-tests.py -j2 --local test-s*
31 # ./run-tests.py -j2 --local test-s*
32 # 7) parallel, coverage, temp install:
32 # 7) parallel, coverage, temp install:
33 # ./run-tests.py -j2 -c test-s* # currently broken
33 # ./run-tests.py -j2 -c test-s* # currently broken
34 # 8) parallel, coverage, local install:
34 # 8) parallel, coverage, local install:
35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
36 # 9) parallel, custom tmp dir:
36 # 9) parallel, custom tmp dir:
37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
38 # 10) parallel, pure, tests that call run-tests:
38 # 10) parallel, pure, tests that call run-tests:
39 # ./run-tests.py --pure `grep -l run-tests.py *.t`
39 # ./run-tests.py --pure `grep -l run-tests.py *.t`
40 #
40 #
41 # (You could use any subset of the tests: test-s* happens to match
41 # (You could use any subset of the tests: test-s* happens to match
42 # enough that it's worth doing parallel runs, few enough that it
42 # enough that it's worth doing parallel runs, few enough that it
43 # completes fairly quickly, includes both shell and Python scripts, and
43 # completes fairly quickly, includes both shell and Python scripts, and
44 # includes some scripts that run daemon processes.)
44 # includes some scripts that run daemon processes.)
45
45
46 from __future__ import absolute_import, print_function
46 from __future__ import absolute_import, print_function
47
47
48 import argparse
48 import argparse
49 import collections
49 import collections
50 import difflib
50 import difflib
51 import distutils.version as version
51 import distutils.version as version
52 import errno
52 import errno
53 import json
53 import json
54 import multiprocessing
54 import multiprocessing
55 import os
55 import os
56 import platform
56 import platform
57 import random
57 import random
58 import re
58 import re
59 import shutil
59 import shutil
60 import signal
60 import signal
61 import socket
61 import socket
62 import subprocess
62 import subprocess
63 import sys
63 import sys
64 import sysconfig
64 import sysconfig
65 import tempfile
65 import tempfile
66 import threading
66 import threading
67 import time
67 import time
68 import unittest
68 import unittest
69 import uuid
69 import uuid
70 import xml.dom.minidom as minidom
70 import xml.dom.minidom as minidom
71
71
72 try:
72 try:
73 import Queue as queue
73 import Queue as queue
74 except ImportError:
74 except ImportError:
75 import queue
75 import queue
76
76
77 try:
77 try:
78 import shlex
78 import shlex
79
79
80 shellquote = shlex.quote
80 shellquote = shlex.quote
81 except (ImportError, AttributeError):
81 except (ImportError, AttributeError):
82 import pipes
82 import pipes
83
83
84 shellquote = pipes.quote
84 shellquote = pipes.quote
85
85
86 processlock = threading.Lock()
86 processlock = threading.Lock()
87
87
88 pygmentspresent = False
88 pygmentspresent = False
89 # ANSI color is unsupported prior to Windows 10
89 # ANSI color is unsupported prior to Windows 10
90 if os.name != 'nt':
90 if os.name != 'nt':
91 try: # is pygments installed
91 try: # is pygments installed
92 import pygments
92 import pygments
93 import pygments.lexers as lexers
93 import pygments.lexers as lexers
94 import pygments.lexer as lexer
94 import pygments.lexer as lexer
95 import pygments.formatters as formatters
95 import pygments.formatters as formatters
96 import pygments.token as token
96 import pygments.token as token
97 import pygments.style as style
97 import pygments.style as style
98
98
99 pygmentspresent = True
99 pygmentspresent = True
100 difflexer = lexers.DiffLexer()
100 difflexer = lexers.DiffLexer()
101 terminal256formatter = formatters.Terminal256Formatter()
101 terminal256formatter = formatters.Terminal256Formatter()
102 except ImportError:
102 except ImportError:
103 pass
103 pass
104
104
105 if pygmentspresent:
105 if pygmentspresent:
106
106
107 class TestRunnerStyle(style.Style):
107 class TestRunnerStyle(style.Style):
108 default_style = ""
108 default_style = ""
109 skipped = token.string_to_tokentype("Token.Generic.Skipped")
109 skipped = token.string_to_tokentype("Token.Generic.Skipped")
110 failed = token.string_to_tokentype("Token.Generic.Failed")
110 failed = token.string_to_tokentype("Token.Generic.Failed")
111 skippedname = token.string_to_tokentype("Token.Generic.SName")
111 skippedname = token.string_to_tokentype("Token.Generic.SName")
112 failedname = token.string_to_tokentype("Token.Generic.FName")
112 failedname = token.string_to_tokentype("Token.Generic.FName")
113 styles = {
113 styles = {
114 skipped: '#e5e5e5',
114 skipped: '#e5e5e5',
115 skippedname: '#00ffff',
115 skippedname: '#00ffff',
116 failed: '#7f0000',
116 failed: '#7f0000',
117 failedname: '#ff0000',
117 failedname: '#ff0000',
118 }
118 }
119
119
120 class TestRunnerLexer(lexer.RegexLexer):
120 class TestRunnerLexer(lexer.RegexLexer):
121 testpattern = r'[\w-]+\.(t|py)(#[a-zA-Z0-9_\-\.]+)?'
121 testpattern = r'[\w-]+\.(t|py)(#[a-zA-Z0-9_\-\.]+)?'
122 tokens = {
122 tokens = {
123 'root': [
123 'root': [
124 (r'^Skipped', token.Generic.Skipped, 'skipped'),
124 (r'^Skipped', token.Generic.Skipped, 'skipped'),
125 (r'^Failed ', token.Generic.Failed, 'failed'),
125 (r'^Failed ', token.Generic.Failed, 'failed'),
126 (r'^ERROR: ', token.Generic.Failed, 'failed'),
126 (r'^ERROR: ', token.Generic.Failed, 'failed'),
127 ],
127 ],
128 'skipped': [
128 'skipped': [
129 (testpattern, token.Generic.SName),
129 (testpattern, token.Generic.SName),
130 (r':.*', token.Generic.Skipped),
130 (r':.*', token.Generic.Skipped),
131 ],
131 ],
132 'failed': [
132 'failed': [
133 (testpattern, token.Generic.FName),
133 (testpattern, token.Generic.FName),
134 (r'(:| ).*', token.Generic.Failed),
134 (r'(:| ).*', token.Generic.Failed),
135 ],
135 ],
136 }
136 }
137
137
138 runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle)
138 runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle)
139 runnerlexer = TestRunnerLexer()
139 runnerlexer = TestRunnerLexer()
140
140
141 origenviron = os.environ.copy()
141 origenviron = os.environ.copy()
142
142
143 if sys.version_info > (3, 5, 0):
143 if sys.version_info > (3, 5, 0):
144 PYTHON3 = True
144 PYTHON3 = True
145 xrange = range # we use xrange in one place, and we'd rather not use range
145 xrange = range # we use xrange in one place, and we'd rather not use range
146
146
147 def _sys2bytes(p):
147 def _sys2bytes(p):
148 if p is None:
148 if p is None:
149 return p
149 return p
150 return p.encode('utf-8')
150 return p.encode('utf-8')
151
151
152 def _bytes2sys(p):
152 def _bytes2sys(p):
153 if p is None:
153 if p is None:
154 return p
154 return p
155 return p.decode('utf-8')
155 return p.decode('utf-8')
156
156
157 osenvironb = getattr(os, 'environb', None)
157 osenvironb = getattr(os, 'environb', None)
158 if osenvironb is None:
158 if osenvironb is None:
159 # Windows lacks os.environb, for instance. A proxy over the real thing
159 # Windows lacks os.environb, for instance. A proxy over the real thing
160 # instead of a copy allows the environment to be updated via bytes on
160 # instead of a copy allows the environment to be updated via bytes on
161 # all platforms.
161 # all platforms.
162 class environbytes(object):
162 class environbytes(object):
163 def __init__(self, strenv):
163 def __init__(self, strenv):
164 self.__len__ = strenv.__len__
164 self.__len__ = strenv.__len__
165 self.clear = strenv.clear
165 self.clear = strenv.clear
166 self._strenv = strenv
166 self._strenv = strenv
167
167
168 def __getitem__(self, k):
168 def __getitem__(self, k):
169 v = self._strenv.__getitem__(_bytes2sys(k))
169 v = self._strenv.__getitem__(_bytes2sys(k))
170 return _sys2bytes(v)
170 return _sys2bytes(v)
171
171
172 def __setitem__(self, k, v):
172 def __setitem__(self, k, v):
173 self._strenv.__setitem__(_bytes2sys(k), _bytes2sys(v))
173 self._strenv.__setitem__(_bytes2sys(k), _bytes2sys(v))
174
174
175 def __delitem__(self, k):
175 def __delitem__(self, k):
176 self._strenv.__delitem__(_bytes2sys(k))
176 self._strenv.__delitem__(_bytes2sys(k))
177
177
178 def __contains__(self, k):
178 def __contains__(self, k):
179 return self._strenv.__contains__(_bytes2sys(k))
179 return self._strenv.__contains__(_bytes2sys(k))
180
180
181 def __iter__(self):
181 def __iter__(self):
182 return iter([_sys2bytes(k) for k in iter(self._strenv)])
182 return iter([_sys2bytes(k) for k in iter(self._strenv)])
183
183
184 def get(self, k, default=None):
184 def get(self, k, default=None):
185 v = self._strenv.get(_bytes2sys(k), _bytes2sys(default))
185 v = self._strenv.get(_bytes2sys(k), _bytes2sys(default))
186 return _sys2bytes(v)
186 return _sys2bytes(v)
187
187
188 def pop(self, k, default=None):
188 def pop(self, k, default=None):
189 v = self._strenv.pop(_bytes2sys(k), _bytes2sys(default))
189 v = self._strenv.pop(_bytes2sys(k), _bytes2sys(default))
190 return _sys2bytes(v)
190 return _sys2bytes(v)
191
191
192 osenvironb = environbytes(os.environ)
192 osenvironb = environbytes(os.environ)
193
193
194 getcwdb = getattr(os, 'getcwdb')
194 getcwdb = getattr(os, 'getcwdb')
195 if not getcwdb or os.name == 'nt':
195 if not getcwdb or os.name == 'nt':
196 getcwdb = lambda: _sys2bytes(os.getcwd())
196 getcwdb = lambda: _sys2bytes(os.getcwd())
197
197
198 elif sys.version_info >= (3, 0, 0):
198 elif sys.version_info >= (3, 0, 0):
199 print(
199 print(
200 '%s is only supported on Python 3.5+ and 2.7, not %s'
200 '%s is only supported on Python 3.5+ and 2.7, not %s'
201 % (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3]))
201 % (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3]))
202 )
202 )
203 sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
203 sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
204 else:
204 else:
205 PYTHON3 = False
205 PYTHON3 = False
206
206
207 # In python 2.x, path operations are generally done using
207 # In python 2.x, path operations are generally done using
208 # bytestrings by default, so we don't have to do any extra
208 # bytestrings by default, so we don't have to do any extra
209 # fiddling there. We define the wrapper functions anyway just to
209 # fiddling there. We define the wrapper functions anyway just to
210 # help keep code consistent between platforms.
210 # help keep code consistent between platforms.
211 def _sys2bytes(p):
211 def _sys2bytes(p):
212 return p
212 return p
213
213
214 _bytes2sys = _sys2bytes
214 _bytes2sys = _sys2bytes
215 osenvironb = os.environ
215 osenvironb = os.environ
216 getcwdb = os.getcwd
216 getcwdb = os.getcwd
217
217
218 # For Windows support
218 # For Windows support
219 wifexited = getattr(os, "WIFEXITED", lambda x: False)
219 wifexited = getattr(os, "WIFEXITED", lambda x: False)
220
220
221 # Whether to use IPv6
221 # Whether to use IPv6
222 def checksocketfamily(name, port=20058):
222 def checksocketfamily(name, port=20058):
223 """return true if we can listen on localhost using family=name
223 """return true if we can listen on localhost using family=name
224
224
225 name should be either 'AF_INET', or 'AF_INET6'.
225 name should be either 'AF_INET', or 'AF_INET6'.
226 port being used is okay - EADDRINUSE is considered as successful.
226 port being used is okay - EADDRINUSE is considered as successful.
227 """
227 """
228 family = getattr(socket, name, None)
228 family = getattr(socket, name, None)
229 if family is None:
229 if family is None:
230 return False
230 return False
231 try:
231 try:
232 s = socket.socket(family, socket.SOCK_STREAM)
232 s = socket.socket(family, socket.SOCK_STREAM)
233 s.bind(('localhost', port))
233 s.bind(('localhost', port))
234 s.close()
234 s.close()
235 return True
235 return True
236 except socket.error as exc:
236 except socket.error as exc:
237 if exc.errno == errno.EADDRINUSE:
237 if exc.errno == errno.EADDRINUSE:
238 return True
238 return True
239 elif exc.errno in (errno.EADDRNOTAVAIL, errno.EPROTONOSUPPORT):
239 elif exc.errno in (errno.EADDRNOTAVAIL, errno.EPROTONOSUPPORT):
240 return False
240 return False
241 else:
241 else:
242 raise
242 raise
243 else:
243 else:
244 return False
244 return False
245
245
246
246
247 # useipv6 will be set by parseargs
247 # useipv6 will be set by parseargs
248 useipv6 = None
248 useipv6 = None
249
249
250
250
251 def checkportisavailable(port):
251 def checkportisavailable(port):
252 """return true if a port seems free to bind on localhost"""
252 """return true if a port seems free to bind on localhost"""
253 if useipv6:
253 if useipv6:
254 family = socket.AF_INET6
254 family = socket.AF_INET6
255 else:
255 else:
256 family = socket.AF_INET
256 family = socket.AF_INET
257 try:
257 try:
258 s = socket.socket(family, socket.SOCK_STREAM)
258 s = socket.socket(family, socket.SOCK_STREAM)
259 s.bind(('localhost', port))
259 s.bind(('localhost', port))
260 s.close()
260 s.close()
261 return True
261 return True
262 except socket.error as exc:
262 except socket.error as exc:
263 if exc.errno not in (
263 if exc.errno not in (
264 errno.EADDRINUSE,
264 errno.EADDRINUSE,
265 errno.EADDRNOTAVAIL,
265 errno.EADDRNOTAVAIL,
266 errno.EPROTONOSUPPORT,
266 errno.EPROTONOSUPPORT,
267 ):
267 ):
268 raise
268 raise
269 return False
269 return False
270
270
271
271
272 closefds = os.name == 'posix'
272 closefds = os.name == 'posix'
273
273
274
274
275 def Popen4(cmd, wd, timeout, env=None):
275 def Popen4(cmd, wd, timeout, env=None):
276 processlock.acquire()
276 processlock.acquire()
277 p = subprocess.Popen(
277 p = subprocess.Popen(
278 _bytes2sys(cmd),
278 _bytes2sys(cmd),
279 shell=True,
279 shell=True,
280 bufsize=-1,
280 bufsize=-1,
281 cwd=_bytes2sys(wd),
281 cwd=_bytes2sys(wd),
282 env=env,
282 env=env,
283 close_fds=closefds,
283 close_fds=closefds,
284 stdin=subprocess.PIPE,
284 stdin=subprocess.PIPE,
285 stdout=subprocess.PIPE,
285 stdout=subprocess.PIPE,
286 stderr=subprocess.STDOUT,
286 stderr=subprocess.STDOUT,
287 )
287 )
288 processlock.release()
288 processlock.release()
289
289
290 p.fromchild = p.stdout
290 p.fromchild = p.stdout
291 p.tochild = p.stdin
291 p.tochild = p.stdin
292 p.childerr = p.stderr
292 p.childerr = p.stderr
293
293
294 p.timeout = False
294 p.timeout = False
295 if timeout:
295 if timeout:
296
296
297 def t():
297 def t():
298 start = time.time()
298 start = time.time()
299 while time.time() - start < timeout and p.returncode is None:
299 while time.time() - start < timeout and p.returncode is None:
300 time.sleep(0.1)
300 time.sleep(0.1)
301 p.timeout = True
301 p.timeout = True
302 if p.returncode is None:
302 if p.returncode is None:
303 terminate(p)
303 terminate(p)
304
304
305 threading.Thread(target=t).start()
305 threading.Thread(target=t).start()
306
306
307 return p
307 return p
308
308
309
309
310 if sys.executable:
310 if sys.executable:
311 sysexecutable = sys.executable
311 sysexecutable = sys.executable
312 elif os.environ.get('PYTHONEXECUTABLE'):
312 elif os.environ.get('PYTHONEXECUTABLE'):
313 sysexecutable = os.environ['PYTHONEXECUTABLE']
313 sysexecutable = os.environ['PYTHONEXECUTABLE']
314 elif os.environ.get('PYTHON'):
314 elif os.environ.get('PYTHON'):
315 sysexecutable = os.environ['PYTHON']
315 sysexecutable = os.environ['PYTHON']
316 else:
316 else:
317 raise AssertionError('Could not find Python interpreter')
317 raise AssertionError('Could not find Python interpreter')
318
318
319 PYTHON = _sys2bytes(sysexecutable.replace('\\', '/'))
319 PYTHON = _sys2bytes(sysexecutable.replace('\\', '/'))
320 IMPL_PATH = b'PYTHONPATH'
320 IMPL_PATH = b'PYTHONPATH'
321 if 'java' in sys.platform:
321 if 'java' in sys.platform:
322 IMPL_PATH = b'JYTHONPATH'
322 IMPL_PATH = b'JYTHONPATH'
323
323
324 default_defaults = {
324 default_defaults = {
325 'jobs': ('HGTEST_JOBS', multiprocessing.cpu_count()),
325 'jobs': ('HGTEST_JOBS', multiprocessing.cpu_count()),
326 'timeout': ('HGTEST_TIMEOUT', 180),
326 'timeout': ('HGTEST_TIMEOUT', 180),
327 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 1500),
327 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 1500),
328 'port': ('HGTEST_PORT', 20059),
328 'port': ('HGTEST_PORT', 20059),
329 'shell': ('HGTEST_SHELL', 'sh'),
329 'shell': ('HGTEST_SHELL', 'sh'),
330 }
330 }
331
331
332 defaults = default_defaults.copy()
332 defaults = default_defaults.copy()
333
333
334
334
335 def canonpath(path):
335 def canonpath(path):
336 return os.path.realpath(os.path.expanduser(path))
336 return os.path.realpath(os.path.expanduser(path))
337
337
338
338
339 def parselistfiles(files, listtype, warn=True):
339 def parselistfiles(files, listtype, warn=True):
340 entries = dict()
340 entries = dict()
341 for filename in files:
341 for filename in files:
342 try:
342 try:
343 path = os.path.expanduser(os.path.expandvars(filename))
343 path = os.path.expanduser(os.path.expandvars(filename))
344 f = open(path, "rb")
344 f = open(path, "rb")
345 except IOError as err:
345 except IOError as err:
346 if err.errno != errno.ENOENT:
346 if err.errno != errno.ENOENT:
347 raise
347 raise
348 if warn:
348 if warn:
349 print("warning: no such %s file: %s" % (listtype, filename))
349 print("warning: no such %s file: %s" % (listtype, filename))
350 continue
350 continue
351
351
352 for line in f.readlines():
352 for line in f.readlines():
353 line = line.split(b'#', 1)[0].strip()
353 line = line.split(b'#', 1)[0].strip()
354 if line:
354 if line:
355 entries[line] = filename
355 entries[line] = filename
356
356
357 f.close()
357 f.close()
358 return entries
358 return entries
359
359
360
360
361 def parsettestcases(path):
361 def parsettestcases(path):
362 """read a .t test file, return a set of test case names
362 """read a .t test file, return a set of test case names
363
363
364 If path does not exist, return an empty set.
364 If path does not exist, return an empty set.
365 """
365 """
366 cases = []
366 cases = []
367 try:
367 try:
368 with open(path, 'rb') as f:
368 with open(path, 'rb') as f:
369 for l in f:
369 for l in f:
370 if l.startswith(b'#testcases '):
370 if l.startswith(b'#testcases '):
371 cases.append(sorted(l[11:].split()))
371 cases.append(sorted(l[11:].split()))
372 except IOError as ex:
372 except IOError as ex:
373 if ex.errno != errno.ENOENT:
373 if ex.errno != errno.ENOENT:
374 raise
374 raise
375 return cases
375 return cases
376
376
377
377
378 def getparser():
378 def getparser():
379 """Obtain the OptionParser used by the CLI."""
379 """Obtain the OptionParser used by the CLI."""
380 parser = argparse.ArgumentParser(usage='%(prog)s [options] [tests]')
380 parser = argparse.ArgumentParser(usage='%(prog)s [options] [tests]')
381
381
382 selection = parser.add_argument_group('Test Selection')
382 selection = parser.add_argument_group('Test Selection')
383 selection.add_argument(
383 selection.add_argument(
384 '--allow-slow-tests',
384 '--allow-slow-tests',
385 action='store_true',
385 action='store_true',
386 help='allow extremely slow tests',
386 help='allow extremely slow tests',
387 )
387 )
388 selection.add_argument(
388 selection.add_argument(
389 "--blacklist",
389 "--blacklist",
390 action="append",
390 action="append",
391 help="skip tests listed in the specified blacklist file",
391 help="skip tests listed in the specified blacklist file",
392 )
392 )
393 selection.add_argument(
393 selection.add_argument(
394 "--changed",
394 "--changed",
395 help="run tests that are changed in parent rev or working directory",
395 help="run tests that are changed in parent rev or working directory",
396 )
396 )
397 selection.add_argument(
397 selection.add_argument(
398 "-k", "--keywords", help="run tests matching keywords"
398 "-k", "--keywords", help="run tests matching keywords"
399 )
399 )
400 selection.add_argument(
400 selection.add_argument(
401 "-r", "--retest", action="store_true", help="retest failed tests"
401 "-r", "--retest", action="store_true", help="retest failed tests"
402 )
402 )
403 selection.add_argument(
403 selection.add_argument(
404 "--test-list",
404 "--test-list",
405 action="append",
405 action="append",
406 help="read tests to run from the specified file",
406 help="read tests to run from the specified file",
407 )
407 )
408 selection.add_argument(
408 selection.add_argument(
409 "--whitelist",
409 "--whitelist",
410 action="append",
410 action="append",
411 help="always run tests listed in the specified whitelist file",
411 help="always run tests listed in the specified whitelist file",
412 )
412 )
413 selection.add_argument(
413 selection.add_argument(
414 'tests', metavar='TESTS', nargs='*', help='Tests to run'
414 'tests', metavar='TESTS', nargs='*', help='Tests to run'
415 )
415 )
416
416
417 harness = parser.add_argument_group('Test Harness Behavior')
417 harness = parser.add_argument_group('Test Harness Behavior')
418 harness.add_argument(
418 harness.add_argument(
419 '--bisect-repo',
419 '--bisect-repo',
420 metavar='bisect_repo',
420 metavar='bisect_repo',
421 help=(
421 help=(
422 "Path of a repo to bisect. Use together with " "--known-good-rev"
422 "Path of a repo to bisect. Use together with " "--known-good-rev"
423 ),
423 ),
424 )
424 )
425 harness.add_argument(
425 harness.add_argument(
426 "-d",
426 "-d",
427 "--debug",
427 "--debug",
428 action="store_true",
428 action="store_true",
429 help="debug mode: write output of test scripts to console"
429 help="debug mode: write output of test scripts to console"
430 " rather than capturing and diffing it (disables timeout)",
430 " rather than capturing and diffing it (disables timeout)",
431 )
431 )
432 harness.add_argument(
432 harness.add_argument(
433 "-f",
433 "-f",
434 "--first",
434 "--first",
435 action="store_true",
435 action="store_true",
436 help="exit on the first test failure",
436 help="exit on the first test failure",
437 )
437 )
438 harness.add_argument(
438 harness.add_argument(
439 "-i",
439 "-i",
440 "--interactive",
440 "--interactive",
441 action="store_true",
441 action="store_true",
442 help="prompt to accept changed output",
442 help="prompt to accept changed output",
443 )
443 )
444 harness.add_argument(
444 harness.add_argument(
445 "-j",
445 "-j",
446 "--jobs",
446 "--jobs",
447 type=int,
447 type=int,
448 help="number of jobs to run in parallel"
448 help="number of jobs to run in parallel"
449 " (default: $%s or %d)" % defaults['jobs'],
449 " (default: $%s or %d)" % defaults['jobs'],
450 )
450 )
451 harness.add_argument(
451 harness.add_argument(
452 "--keep-tmpdir",
452 "--keep-tmpdir",
453 action="store_true",
453 action="store_true",
454 help="keep temporary directory after running tests",
454 help="keep temporary directory after running tests",
455 )
455 )
456 harness.add_argument(
456 harness.add_argument(
457 '--known-good-rev',
457 '--known-good-rev',
458 metavar="known_good_rev",
458 metavar="known_good_rev",
459 help=(
459 help=(
460 "Automatically bisect any failures using this "
460 "Automatically bisect any failures using this "
461 "revision as a known-good revision."
461 "revision as a known-good revision."
462 ),
462 ),
463 )
463 )
464 harness.add_argument(
464 harness.add_argument(
465 "--list-tests",
465 "--list-tests",
466 action="store_true",
466 action="store_true",
467 help="list tests instead of running them",
467 help="list tests instead of running them",
468 )
468 )
469 harness.add_argument(
469 harness.add_argument(
470 "--loop", action="store_true", help="loop tests repeatedly"
470 "--loop", action="store_true", help="loop tests repeatedly"
471 )
471 )
472 harness.add_argument(
472 harness.add_argument(
473 '--random', action="store_true", help='run tests in random order'
473 '--random', action="store_true", help='run tests in random order'
474 )
474 )
475 harness.add_argument(
475 harness.add_argument(
476 '--order-by-runtime',
476 '--order-by-runtime',
477 action="store_true",
477 action="store_true",
478 help='run slowest tests first, according to .testtimes',
478 help='run slowest tests first, according to .testtimes',
479 )
479 )
480 harness.add_argument(
480 harness.add_argument(
481 "-p",
481 "-p",
482 "--port",
482 "--port",
483 type=int,
483 type=int,
484 help="port on which servers should listen"
484 help="port on which servers should listen"
485 " (default: $%s or %d)" % defaults['port'],
485 " (default: $%s or %d)" % defaults['port'],
486 )
486 )
487 harness.add_argument(
487 harness.add_argument(
488 '--profile-runner',
488 '--profile-runner',
489 action='store_true',
489 action='store_true',
490 help='run statprof on run-tests',
490 help='run statprof on run-tests',
491 )
491 )
492 harness.add_argument(
492 harness.add_argument(
493 "-R", "--restart", action="store_true", help="restart at last error"
493 "-R", "--restart", action="store_true", help="restart at last error"
494 )
494 )
495 harness.add_argument(
495 harness.add_argument(
496 "--runs-per-test",
496 "--runs-per-test",
497 type=int,
497 type=int,
498 dest="runs_per_test",
498 dest="runs_per_test",
499 help="run each test N times (default=1)",
499 help="run each test N times (default=1)",
500 default=1,
500 default=1,
501 )
501 )
502 harness.add_argument(
502 harness.add_argument(
503 "--shell", help="shell to use (default: $%s or %s)" % defaults['shell']
503 "--shell", help="shell to use (default: $%s or %s)" % defaults['shell']
504 )
504 )
505 harness.add_argument(
505 harness.add_argument(
506 '--showchannels', action='store_true', help='show scheduling channels'
506 '--showchannels', action='store_true', help='show scheduling channels'
507 )
507 )
508 harness.add_argument(
508 harness.add_argument(
509 "--slowtimeout",
509 "--slowtimeout",
510 type=int,
510 type=int,
511 help="kill errant slow tests after SLOWTIMEOUT seconds"
511 help="kill errant slow tests after SLOWTIMEOUT seconds"
512 " (default: $%s or %d)" % defaults['slowtimeout'],
512 " (default: $%s or %d)" % defaults['slowtimeout'],
513 )
513 )
514 harness.add_argument(
514 harness.add_argument(
515 "-t",
515 "-t",
516 "--timeout",
516 "--timeout",
517 type=int,
517 type=int,
518 help="kill errant tests after TIMEOUT seconds"
518 help="kill errant tests after TIMEOUT seconds"
519 " (default: $%s or %d)" % defaults['timeout'],
519 " (default: $%s or %d)" % defaults['timeout'],
520 )
520 )
521 harness.add_argument(
521 harness.add_argument(
522 "--tmpdir",
522 "--tmpdir",
523 help="run tests in the given temporary directory"
523 help="run tests in the given temporary directory"
524 " (implies --keep-tmpdir)",
524 " (implies --keep-tmpdir)",
525 )
525 )
526 harness.add_argument(
526 harness.add_argument(
527 "-v", "--verbose", action="store_true", help="output verbose messages"
527 "-v", "--verbose", action="store_true", help="output verbose messages"
528 )
528 )
529
529
530 hgconf = parser.add_argument_group('Mercurial Configuration')
530 hgconf = parser.add_argument_group('Mercurial Configuration')
531 hgconf.add_argument(
531 hgconf.add_argument(
532 "--chg",
532 "--chg",
533 action="store_true",
533 action="store_true",
534 help="install and use chg wrapper in place of hg",
534 help="install and use chg wrapper in place of hg",
535 )
535 )
536 hgconf.add_argument(
536 hgconf.add_argument(
537 "--chg-debug", action="store_true", help="show chg debug logs",
537 "--chg-debug", action="store_true", help="show chg debug logs",
538 )
538 )
539 hgconf.add_argument("--compiler", help="compiler to build with")
539 hgconf.add_argument("--compiler", help="compiler to build with")
540 hgconf.add_argument(
540 hgconf.add_argument(
541 '--extra-config-opt',
541 '--extra-config-opt',
542 action="append",
542 action="append",
543 default=[],
543 default=[],
544 help='set the given config opt in the test hgrc',
544 help='set the given config opt in the test hgrc',
545 )
545 )
546 hgconf.add_argument(
546 hgconf.add_argument(
547 "-l",
547 "-l",
548 "--local",
548 "--local",
549 action="store_true",
549 action="store_true",
550 help="shortcut for --with-hg=<testdir>/../hg, "
550 help="shortcut for --with-hg=<testdir>/../hg, "
551 "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set",
551 "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set",
552 )
552 )
553 hgconf.add_argument(
553 hgconf.add_argument(
554 "--ipv6",
554 "--ipv6",
555 action="store_true",
555 action="store_true",
556 help="prefer IPv6 to IPv4 for network related tests",
556 help="prefer IPv6 to IPv4 for network related tests",
557 )
557 )
558 hgconf.add_argument(
558 hgconf.add_argument(
559 "--pure",
559 "--pure",
560 action="store_true",
560 action="store_true",
561 help="use pure Python code instead of C extensions",
561 help="use pure Python code instead of C extensions",
562 )
562 )
563 hgconf.add_argument(
563 hgconf.add_argument(
564 "--rust",
564 "--rust",
565 action="store_true",
565 action="store_true",
566 help="use Rust code alongside C extensions",
566 help="use Rust code alongside C extensions",
567 )
567 )
568 hgconf.add_argument(
568 hgconf.add_argument(
569 "--no-rust",
569 "--no-rust",
570 action="store_true",
570 action="store_true",
571 help="do not use Rust code even if compiled",
571 help="do not use Rust code even if compiled",
572 )
572 )
573 hgconf.add_argument(
573 hgconf.add_argument(
574 "--with-chg",
574 "--with-chg",
575 metavar="CHG",
575 metavar="CHG",
576 help="use specified chg wrapper in place of hg",
576 help="use specified chg wrapper in place of hg",
577 )
577 )
578 hgconf.add_argument(
578 hgconf.add_argument(
579 "--with-hg",
579 "--with-hg",
580 metavar="HG",
580 metavar="HG",
581 help="test using specified hg script rather than a "
581 help="test using specified hg script rather than a "
582 "temporary installation",
582 "temporary installation",
583 )
583 )
584
584
585 reporting = parser.add_argument_group('Results Reporting')
585 reporting = parser.add_argument_group('Results Reporting')
586 reporting.add_argument(
586 reporting.add_argument(
587 "-C",
587 "-C",
588 "--annotate",
588 "--annotate",
589 action="store_true",
589 action="store_true",
590 help="output files annotated with coverage",
590 help="output files annotated with coverage",
591 )
591 )
592 reporting.add_argument(
592 reporting.add_argument(
593 "--color",
593 "--color",
594 choices=["always", "auto", "never"],
594 choices=["always", "auto", "never"],
595 default=os.environ.get('HGRUNTESTSCOLOR', 'auto'),
595 default=os.environ.get('HGRUNTESTSCOLOR', 'auto'),
596 help="colorisation: always|auto|never (default: auto)",
596 help="colorisation: always|auto|never (default: auto)",
597 )
597 )
598 reporting.add_argument(
598 reporting.add_argument(
599 "-c",
599 "-c",
600 "--cover",
600 "--cover",
601 action="store_true",
601 action="store_true",
602 help="print a test coverage report",
602 help="print a test coverage report",
603 )
603 )
604 reporting.add_argument(
604 reporting.add_argument(
605 '--exceptions',
605 '--exceptions',
606 action='store_true',
606 action='store_true',
607 help='log all exceptions and generate an exception report',
607 help='log all exceptions and generate an exception report',
608 )
608 )
609 reporting.add_argument(
609 reporting.add_argument(
610 "-H",
610 "-H",
611 "--htmlcov",
611 "--htmlcov",
612 action="store_true",
612 action="store_true",
613 help="create an HTML report of the coverage of the files",
613 help="create an HTML report of the coverage of the files",
614 )
614 )
615 reporting.add_argument(
615 reporting.add_argument(
616 "--json",
616 "--json",
617 action="store_true",
617 action="store_true",
618 help="store test result data in 'report.json' file",
618 help="store test result data in 'report.json' file",
619 )
619 )
620 reporting.add_argument(
620 reporting.add_argument(
621 "--outputdir",
621 "--outputdir",
622 help="directory to write error logs to (default=test directory)",
622 help="directory to write error logs to (default=test directory)",
623 )
623 )
624 reporting.add_argument(
624 reporting.add_argument(
625 "-n", "--nodiff", action="store_true", help="skip showing test changes"
625 "-n", "--nodiff", action="store_true", help="skip showing test changes"
626 )
626 )
627 reporting.add_argument(
627 reporting.add_argument(
628 "-S",
628 "-S",
629 "--noskips",
629 "--noskips",
630 action="store_true",
630 action="store_true",
631 help="don't report skip tests verbosely",
631 help="don't report skip tests verbosely",
632 )
632 )
633 reporting.add_argument(
633 reporting.add_argument(
634 "--time", action="store_true", help="time how long each test takes"
634 "--time", action="store_true", help="time how long each test takes"
635 )
635 )
636 reporting.add_argument("--view", help="external diff viewer")
636 reporting.add_argument("--view", help="external diff viewer")
637 reporting.add_argument(
637 reporting.add_argument(
638 "--xunit", help="record xunit results at specified path"
638 "--xunit", help="record xunit results at specified path"
639 )
639 )
640
640
641 for option, (envvar, default) in defaults.items():
641 for option, (envvar, default) in defaults.items():
642 defaults[option] = type(default)(os.environ.get(envvar, default))
642 defaults[option] = type(default)(os.environ.get(envvar, default))
643 parser.set_defaults(**defaults)
643 parser.set_defaults(**defaults)
644
644
645 return parser
645 return parser
646
646
647
647
648 def parseargs(args, parser):
648 def parseargs(args, parser):
649 """Parse arguments with our OptionParser and validate results."""
649 """Parse arguments with our OptionParser and validate results."""
650 options = parser.parse_args(args)
650 options = parser.parse_args(args)
651
651
652 # jython is always pure
652 # jython is always pure
653 if 'java' in sys.platform or '__pypy__' in sys.modules:
653 if 'java' in sys.platform or '__pypy__' in sys.modules:
654 options.pure = True
654 options.pure = True
655
655
656 if platform.python_implementation() != 'CPython' and options.rust:
656 if platform.python_implementation() != 'CPython' and options.rust:
657 parser.error('Rust extensions are only available with CPython')
657 parser.error('Rust extensions are only available with CPython')
658
658
659 if options.pure and options.rust:
659 if options.pure and options.rust:
660 parser.error('--rust cannot be used with --pure')
660 parser.error('--rust cannot be used with --pure')
661
661
662 if options.rust and options.no_rust:
662 if options.rust and options.no_rust:
663 parser.error('--rust cannot be used with --no-rust')
663 parser.error('--rust cannot be used with --no-rust')
664
664
665 if options.local:
665 if options.local:
666 if options.with_hg or options.with_chg:
666 if options.with_hg or options.with_chg:
667 parser.error('--local cannot be used with --with-hg or --with-chg')
667 parser.error('--local cannot be used with --with-hg or --with-chg')
668 testdir = os.path.dirname(_sys2bytes(canonpath(sys.argv[0])))
668 testdir = os.path.dirname(_sys2bytes(canonpath(sys.argv[0])))
669 reporootdir = os.path.dirname(testdir)
669 reporootdir = os.path.dirname(testdir)
670 pathandattrs = [(b'hg', 'with_hg')]
670 pathandattrs = [(b'hg', 'with_hg')]
671 if options.chg:
671 if options.chg:
672 pathandattrs.append((b'contrib/chg/chg', 'with_chg'))
672 pathandattrs.append((b'contrib/chg/chg', 'with_chg'))
673 for relpath, attr in pathandattrs:
673 for relpath, attr in pathandattrs:
674 binpath = os.path.join(reporootdir, relpath)
674 binpath = os.path.join(reporootdir, relpath)
675 if os.name != 'nt' and not os.access(binpath, os.X_OK):
675 if os.name != 'nt' and not os.access(binpath, os.X_OK):
676 parser.error(
676 parser.error(
677 '--local specified, but %r not found or '
677 '--local specified, but %r not found or '
678 'not executable' % binpath
678 'not executable' % binpath
679 )
679 )
680 setattr(options, attr, _bytes2sys(binpath))
680 setattr(options, attr, _bytes2sys(binpath))
681
681
682 if options.with_hg:
682 if options.with_hg:
683 options.with_hg = canonpath(_sys2bytes(options.with_hg))
683 options.with_hg = canonpath(_sys2bytes(options.with_hg))
684 if not (
684 if not (
685 os.path.isfile(options.with_hg)
685 os.path.isfile(options.with_hg)
686 and os.access(options.with_hg, os.X_OK)
686 and os.access(options.with_hg, os.X_OK)
687 ):
687 ):
688 parser.error('--with-hg must specify an executable hg script')
688 parser.error('--with-hg must specify an executable hg script')
689 if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']:
689 if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']:
690 sys.stderr.write('warning: --with-hg should specify an hg script\n')
690 sys.stderr.write('warning: --with-hg should specify an hg script\n')
691 sys.stderr.flush()
691 sys.stderr.flush()
692
692
693 if (options.chg or options.with_chg) and os.name == 'nt':
693 if (options.chg or options.with_chg) and os.name == 'nt':
694 parser.error('chg does not work on %s' % os.name)
694 parser.error('chg does not work on %s' % os.name)
695 if options.with_chg:
695 if options.with_chg:
696 options.chg = False # no installation to temporary location
696 options.chg = False # no installation to temporary location
697 options.with_chg = canonpath(_sys2bytes(options.with_chg))
697 options.with_chg = canonpath(_sys2bytes(options.with_chg))
698 if not (
698 if not (
699 os.path.isfile(options.with_chg)
699 os.path.isfile(options.with_chg)
700 and os.access(options.with_chg, os.X_OK)
700 and os.access(options.with_chg, os.X_OK)
701 ):
701 ):
702 parser.error('--with-chg must specify a chg executable')
702 parser.error('--with-chg must specify a chg executable')
703 if options.chg and options.with_hg:
703 if options.chg and options.with_hg:
704 # chg shares installation location with hg
704 # chg shares installation location with hg
705 parser.error(
705 parser.error(
706 '--chg does not work when --with-hg is specified '
706 '--chg does not work when --with-hg is specified '
707 '(use --with-chg instead)'
707 '(use --with-chg instead)'
708 )
708 )
709
709
710 if options.color == 'always' and not pygmentspresent:
710 if options.color == 'always' and not pygmentspresent:
711 sys.stderr.write(
711 sys.stderr.write(
712 'warning: --color=always ignored because '
712 'warning: --color=always ignored because '
713 'pygments is not installed\n'
713 'pygments is not installed\n'
714 )
714 )
715
715
716 if options.bisect_repo and not options.known_good_rev:
716 if options.bisect_repo and not options.known_good_rev:
717 parser.error("--bisect-repo cannot be used without --known-good-rev")
717 parser.error("--bisect-repo cannot be used without --known-good-rev")
718
718
719 global useipv6
719 global useipv6
720 if options.ipv6:
720 if options.ipv6:
721 useipv6 = checksocketfamily('AF_INET6')
721 useipv6 = checksocketfamily('AF_INET6')
722 else:
722 else:
723 # only use IPv6 if IPv4 is unavailable and IPv6 is available
723 # only use IPv6 if IPv4 is unavailable and IPv6 is available
724 useipv6 = (not checksocketfamily('AF_INET')) and checksocketfamily(
724 useipv6 = (not checksocketfamily('AF_INET')) and checksocketfamily(
725 'AF_INET6'
725 'AF_INET6'
726 )
726 )
727
727
728 options.anycoverage = options.cover or options.annotate or options.htmlcov
728 options.anycoverage = options.cover or options.annotate or options.htmlcov
729 if options.anycoverage:
729 if options.anycoverage:
730 try:
730 try:
731 import coverage
731 import coverage
732
732
733 covver = version.StrictVersion(coverage.__version__).version
733 covver = version.StrictVersion(coverage.__version__).version
734 if covver < (3, 3):
734 if covver < (3, 3):
735 parser.error('coverage options require coverage 3.3 or later')
735 parser.error('coverage options require coverage 3.3 or later')
736 except ImportError:
736 except ImportError:
737 parser.error('coverage options now require the coverage package')
737 parser.error('coverage options now require the coverage package')
738
738
739 if options.anycoverage and options.local:
739 if options.anycoverage and options.local:
740 # this needs some path mangling somewhere, I guess
740 # this needs some path mangling somewhere, I guess
741 parser.error(
741 parser.error(
742 "sorry, coverage options do not work when --local " "is specified"
742 "sorry, coverage options do not work when --local " "is specified"
743 )
743 )
744
744
745 if options.anycoverage and options.with_hg:
745 if options.anycoverage and options.with_hg:
746 parser.error(
746 parser.error(
747 "sorry, coverage options do not work when --with-hg " "is specified"
747 "sorry, coverage options do not work when --with-hg " "is specified"
748 )
748 )
749
749
750 global verbose
750 global verbose
751 if options.verbose:
751 if options.verbose:
752 verbose = ''
752 verbose = ''
753
753
754 if options.tmpdir:
754 if options.tmpdir:
755 options.tmpdir = canonpath(options.tmpdir)
755 options.tmpdir = canonpath(options.tmpdir)
756
756
757 if options.jobs < 1:
757 if options.jobs < 1:
758 parser.error('--jobs must be positive')
758 parser.error('--jobs must be positive')
759 if options.interactive and options.debug:
759 if options.interactive and options.debug:
760 parser.error("-i/--interactive and -d/--debug are incompatible")
760 parser.error("-i/--interactive and -d/--debug are incompatible")
761 if options.debug:
761 if options.debug:
762 if options.timeout != defaults['timeout']:
762 if options.timeout != defaults['timeout']:
763 sys.stderr.write('warning: --timeout option ignored with --debug\n')
763 sys.stderr.write('warning: --timeout option ignored with --debug\n')
764 if options.slowtimeout != defaults['slowtimeout']:
764 if options.slowtimeout != defaults['slowtimeout']:
765 sys.stderr.write(
765 sys.stderr.write(
766 'warning: --slowtimeout option ignored with --debug\n'
766 'warning: --slowtimeout option ignored with --debug\n'
767 )
767 )
768 options.timeout = 0
768 options.timeout = 0
769 options.slowtimeout = 0
769 options.slowtimeout = 0
770
770
771 if options.blacklist:
771 if options.blacklist:
772 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
772 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
773 if options.whitelist:
773 if options.whitelist:
774 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
774 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
775 else:
775 else:
776 options.whitelisted = {}
776 options.whitelisted = {}
777
777
778 if options.showchannels:
778 if options.showchannels:
779 options.nodiff = True
779 options.nodiff = True
780
780
781 return options
781 return options
782
782
783
783
784 def rename(src, dst):
784 def rename(src, dst):
785 """Like os.rename(), trade atomicity and opened files friendliness
785 """Like os.rename(), trade atomicity and opened files friendliness
786 for existing destination support.
786 for existing destination support.
787 """
787 """
788 shutil.copy(src, dst)
788 shutil.copy(src, dst)
789 os.remove(src)
789 os.remove(src)
790
790
791
791
792 def makecleanable(path):
792 def makecleanable(path):
793 """Try to fix directory permission recursively so that the entire tree
793 """Try to fix directory permission recursively so that the entire tree
794 can be deleted"""
794 can be deleted"""
795 for dirpath, dirnames, _filenames in os.walk(path, topdown=True):
795 for dirpath, dirnames, _filenames in os.walk(path, topdown=True):
796 for d in dirnames:
796 for d in dirnames:
797 p = os.path.join(dirpath, d)
797 p = os.path.join(dirpath, d)
798 try:
798 try:
799 os.chmod(p, os.stat(p).st_mode & 0o777 | 0o700) # chmod u+rwx
799 os.chmod(p, os.stat(p).st_mode & 0o777 | 0o700) # chmod u+rwx
800 except OSError:
800 except OSError:
801 pass
801 pass
802
802
803
803
804 _unified_diff = difflib.unified_diff
804 _unified_diff = difflib.unified_diff
805 if PYTHON3:
805 if PYTHON3:
806 import functools
806 import functools
807
807
808 _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
808 _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
809
809
810
810
811 def getdiff(expected, output, ref, err):
811 def getdiff(expected, output, ref, err):
812 servefail = False
812 servefail = False
813 lines = []
813 lines = []
814 for line in _unified_diff(expected, output, ref, err):
814 for line in _unified_diff(expected, output, ref, err):
815 if line.startswith(b'+++') or line.startswith(b'---'):
815 if line.startswith(b'+++') or line.startswith(b'---'):
816 line = line.replace(b'\\', b'/')
816 line = line.replace(b'\\', b'/')
817 if line.endswith(b' \n'):
817 if line.endswith(b' \n'):
818 line = line[:-2] + b'\n'
818 line = line[:-2] + b'\n'
819 lines.append(line)
819 lines.append(line)
820 if not servefail and line.startswith(
820 if not servefail and line.startswith(
821 b'+ abort: child process failed to start'
821 b'+ abort: child process failed to start'
822 ):
822 ):
823 servefail = True
823 servefail = True
824
824
825 return servefail, lines
825 return servefail, lines
826
826
827
827
828 verbose = False
828 verbose = False
829
829
830
830
831 def vlog(*msg):
831 def vlog(*msg):
832 """Log only when in verbose mode."""
832 """Log only when in verbose mode."""
833 if verbose is False:
833 if verbose is False:
834 return
834 return
835
835
836 return log(*msg)
836 return log(*msg)
837
837
838
838
839 # Bytes that break XML even in a CDATA block: control characters 0-31
839 # Bytes that break XML even in a CDATA block: control characters 0-31
840 # sans \t, \n and \r
840 # sans \t, \n and \r
841 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
841 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
842
842
843 # Match feature conditionalized output lines in the form, capturing the feature
843 # Match feature conditionalized output lines in the form, capturing the feature
844 # list in group 2, and the preceeding line output in group 1:
844 # list in group 2, and the preceeding line output in group 1:
845 #
845 #
846 # output..output (feature !)\n
846 # output..output (feature !)\n
847 optline = re.compile(br'(.*) \((.+?) !\)\n$')
847 optline = re.compile(br'(.*) \((.+?) !\)\n$')
848
848
849
849
850 def cdatasafe(data):
850 def cdatasafe(data):
851 """Make a string safe to include in a CDATA block.
851 """Make a string safe to include in a CDATA block.
852
852
853 Certain control characters are illegal in a CDATA block, and
853 Certain control characters are illegal in a CDATA block, and
854 there's no way to include a ]]> in a CDATA either. This function
854 there's no way to include a ]]> in a CDATA either. This function
855 replaces illegal bytes with ? and adds a space between the ]] so
855 replaces illegal bytes with ? and adds a space between the ]] so
856 that it won't break the CDATA block.
856 that it won't break the CDATA block.
857 """
857 """
858 return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
858 return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
859
859
860
860
861 def log(*msg):
861 def log(*msg):
862 """Log something to stdout.
862 """Log something to stdout.
863
863
864 Arguments are strings to print.
864 Arguments are strings to print.
865 """
865 """
866 with iolock:
866 with iolock:
867 if verbose:
867 if verbose:
868 print(verbose, end=' ')
868 print(verbose, end=' ')
869 for m in msg:
869 for m in msg:
870 print(m, end=' ')
870 print(m, end=' ')
871 print()
871 print()
872 sys.stdout.flush()
872 sys.stdout.flush()
873
873
874
874
875 def highlightdiff(line, color):
875 def highlightdiff(line, color):
876 if not color:
876 if not color:
877 return line
877 return line
878 assert pygmentspresent
878 assert pygmentspresent
879 return pygments.highlight(
879 return pygments.highlight(
880 line.decode('latin1'), difflexer, terminal256formatter
880 line.decode('latin1'), difflexer, terminal256formatter
881 ).encode('latin1')
881 ).encode('latin1')
882
882
883
883
884 def highlightmsg(msg, color):
884 def highlightmsg(msg, color):
885 if not color:
885 if not color:
886 return msg
886 return msg
887 assert pygmentspresent
887 assert pygmentspresent
888 return pygments.highlight(msg, runnerlexer, runnerformatter)
888 return pygments.highlight(msg, runnerlexer, runnerformatter)
889
889
890
890
891 def terminate(proc):
891 def terminate(proc):
892 """Terminate subprocess"""
892 """Terminate subprocess"""
893 vlog('# Terminating process %d' % proc.pid)
893 vlog('# Terminating process %d' % proc.pid)
894 try:
894 try:
895 proc.terminate()
895 proc.terminate()
896 except OSError:
896 except OSError:
897 pass
897 pass
898
898
899
899
900 def killdaemons(pidfile):
900 def killdaemons(pidfile):
901 import killdaemons as killmod
901 import killdaemons as killmod
902
902
903 return killmod.killdaemons(pidfile, tryhard=False, remove=True, logfn=vlog)
903 return killmod.killdaemons(pidfile, tryhard=False, remove=True, logfn=vlog)
904
904
905
905
906 class Test(unittest.TestCase):
906 class Test(unittest.TestCase):
907 """Encapsulates a single, runnable test.
907 """Encapsulates a single, runnable test.
908
908
909 While this class conforms to the unittest.TestCase API, it differs in that
909 While this class conforms to the unittest.TestCase API, it differs in that
910 instances need to be instantiated manually. (Typically, unittest.TestCase
910 instances need to be instantiated manually. (Typically, unittest.TestCase
911 classes are instantiated automatically by scanning modules.)
911 classes are instantiated automatically by scanning modules.)
912 """
912 """
913
913
914 # Status code reserved for skipped tests (used by hghave).
914 # Status code reserved for skipped tests (used by hghave).
915 SKIPPED_STATUS = 80
915 SKIPPED_STATUS = 80
916
916
917 def __init__(
917 def __init__(
918 self,
918 self,
919 path,
919 path,
920 outputdir,
920 outputdir,
921 tmpdir,
921 tmpdir,
922 keeptmpdir=False,
922 keeptmpdir=False,
923 debug=False,
923 debug=False,
924 first=False,
924 first=False,
925 timeout=None,
925 timeout=None,
926 startport=None,
926 startport=None,
927 extraconfigopts=None,
927 extraconfigopts=None,
928 shell=None,
928 shell=None,
929 hgcommand=None,
929 hgcommand=None,
930 rhgcommand=None,
930 slowtimeout=None,
931 slowtimeout=None,
931 usechg=False,
932 usechg=False,
932 chgdebug=False,
933 chgdebug=False,
933 useipv6=False,
934 useipv6=False,
934 ):
935 ):
935 """Create a test from parameters.
936 """Create a test from parameters.
936
937
937 path is the full path to the file defining the test.
938 path is the full path to the file defining the test.
938
939
939 tmpdir is the main temporary directory to use for this test.
940 tmpdir is the main temporary directory to use for this test.
940
941
941 keeptmpdir determines whether to keep the test's temporary directory
942 keeptmpdir determines whether to keep the test's temporary directory
942 after execution. It defaults to removal (False).
943 after execution. It defaults to removal (False).
943
944
944 debug mode will make the test execute verbosely, with unfiltered
945 debug mode will make the test execute verbosely, with unfiltered
945 output.
946 output.
946
947
947 timeout controls the maximum run time of the test. It is ignored when
948 timeout controls the maximum run time of the test. It is ignored when
948 debug is True. See slowtimeout for tests with #require slow.
949 debug is True. See slowtimeout for tests with #require slow.
949
950
950 slowtimeout overrides timeout if the test has #require slow.
951 slowtimeout overrides timeout if the test has #require slow.
951
952
952 startport controls the starting port number to use for this test. Each
953 startport controls the starting port number to use for this test. Each
953 test will reserve 3 port numbers for execution. It is the caller's
954 test will reserve 3 port numbers for execution. It is the caller's
954 responsibility to allocate a non-overlapping port range to Test
955 responsibility to allocate a non-overlapping port range to Test
955 instances.
956 instances.
956
957
957 extraconfigopts is an iterable of extra hgrc config options. Values
958 extraconfigopts is an iterable of extra hgrc config options. Values
958 must have the form "key=value" (something understood by hgrc). Values
959 must have the form "key=value" (something understood by hgrc). Values
959 of the form "foo.key=value" will result in "[foo] key=value".
960 of the form "foo.key=value" will result in "[foo] key=value".
960
961
961 shell is the shell to execute tests in.
962 shell is the shell to execute tests in.
962 """
963 """
963 if timeout is None:
964 if timeout is None:
964 timeout = defaults['timeout']
965 timeout = defaults['timeout']
965 if startport is None:
966 if startport is None:
966 startport = defaults['port']
967 startport = defaults['port']
967 if slowtimeout is None:
968 if slowtimeout is None:
968 slowtimeout = defaults['slowtimeout']
969 slowtimeout = defaults['slowtimeout']
969 self.path = path
970 self.path = path
970 self.bname = os.path.basename(path)
971 self.bname = os.path.basename(path)
971 self.name = _bytes2sys(self.bname)
972 self.name = _bytes2sys(self.bname)
972 self._testdir = os.path.dirname(path)
973 self._testdir = os.path.dirname(path)
973 self._outputdir = outputdir
974 self._outputdir = outputdir
974 self._tmpname = os.path.basename(path)
975 self._tmpname = os.path.basename(path)
975 self.errpath = os.path.join(self._outputdir, b'%s.err' % self.bname)
976 self.errpath = os.path.join(self._outputdir, b'%s.err' % self.bname)
976
977
977 self._threadtmp = tmpdir
978 self._threadtmp = tmpdir
978 self._keeptmpdir = keeptmpdir
979 self._keeptmpdir = keeptmpdir
979 self._debug = debug
980 self._debug = debug
980 self._first = first
981 self._first = first
981 self._timeout = timeout
982 self._timeout = timeout
982 self._slowtimeout = slowtimeout
983 self._slowtimeout = slowtimeout
983 self._startport = startport
984 self._startport = startport
984 self._extraconfigopts = extraconfigopts or []
985 self._extraconfigopts = extraconfigopts or []
985 self._shell = _sys2bytes(shell)
986 self._shell = _sys2bytes(shell)
986 self._hgcommand = hgcommand or b'hg'
987 self._hgcommand = hgcommand or b'hg'
988 self._rhgcommand = rhgcommand or _sys2bytes(
989 os.path.abspath('./rust/target/release/rhg')
990 )
987 self._usechg = usechg
991 self._usechg = usechg
988 self._chgdebug = chgdebug
992 self._chgdebug = chgdebug
989 self._useipv6 = useipv6
993 self._useipv6 = useipv6
990
994
991 self._aborted = False
995 self._aborted = False
992 self._daemonpids = []
996 self._daemonpids = []
993 self._finished = None
997 self._finished = None
994 self._ret = None
998 self._ret = None
995 self._out = None
999 self._out = None
996 self._skipped = None
1000 self._skipped = None
997 self._testtmp = None
1001 self._testtmp = None
998 self._chgsockdir = None
1002 self._chgsockdir = None
999
1003
1000 self._refout = self.readrefout()
1004 self._refout = self.readrefout()
1001
1005
1002 def readrefout(self):
1006 def readrefout(self):
1003 """read reference output"""
1007 """read reference output"""
1004 # If we're not in --debug mode and reference output file exists,
1008 # If we're not in --debug mode and reference output file exists,
1005 # check test output against it.
1009 # check test output against it.
1006 if self._debug:
1010 if self._debug:
1007 return None # to match "out is None"
1011 return None # to match "out is None"
1008 elif os.path.exists(self.refpath):
1012 elif os.path.exists(self.refpath):
1009 with open(self.refpath, 'rb') as f:
1013 with open(self.refpath, 'rb') as f:
1010 return f.read().splitlines(True)
1014 return f.read().splitlines(True)
1011 else:
1015 else:
1012 return []
1016 return []
1013
1017
1014 # needed to get base class __repr__ running
1018 # needed to get base class __repr__ running
1015 @property
1019 @property
1016 def _testMethodName(self):
1020 def _testMethodName(self):
1017 return self.name
1021 return self.name
1018
1022
1019 def __str__(self):
1023 def __str__(self):
1020 return self.name
1024 return self.name
1021
1025
1022 def shortDescription(self):
1026 def shortDescription(self):
1023 return self.name
1027 return self.name
1024
1028
1025 def setUp(self):
1029 def setUp(self):
1026 """Tasks to perform before run()."""
1030 """Tasks to perform before run()."""
1027 self._finished = False
1031 self._finished = False
1028 self._ret = None
1032 self._ret = None
1029 self._out = None
1033 self._out = None
1030 self._skipped = None
1034 self._skipped = None
1031
1035
1032 try:
1036 try:
1033 os.mkdir(self._threadtmp)
1037 os.mkdir(self._threadtmp)
1034 except OSError as e:
1038 except OSError as e:
1035 if e.errno != errno.EEXIST:
1039 if e.errno != errno.EEXIST:
1036 raise
1040 raise
1037
1041
1038 name = self._tmpname
1042 name = self._tmpname
1039 self._testtmp = os.path.join(self._threadtmp, name)
1043 self._testtmp = os.path.join(self._threadtmp, name)
1040 os.mkdir(self._testtmp)
1044 os.mkdir(self._testtmp)
1041
1045
1042 # Remove any previous output files.
1046 # Remove any previous output files.
1043 if os.path.exists(self.errpath):
1047 if os.path.exists(self.errpath):
1044 try:
1048 try:
1045 os.remove(self.errpath)
1049 os.remove(self.errpath)
1046 except OSError as e:
1050 except OSError as e:
1047 # We might have raced another test to clean up a .err
1051 # We might have raced another test to clean up a .err
1048 # file, so ignore ENOENT when removing a previous .err
1052 # file, so ignore ENOENT when removing a previous .err
1049 # file.
1053 # file.
1050 if e.errno != errno.ENOENT:
1054 if e.errno != errno.ENOENT:
1051 raise
1055 raise
1052
1056
1053 if self._usechg:
1057 if self._usechg:
1054 self._chgsockdir = os.path.join(
1058 self._chgsockdir = os.path.join(
1055 self._threadtmp, b'%s.chgsock' % name
1059 self._threadtmp, b'%s.chgsock' % name
1056 )
1060 )
1057 os.mkdir(self._chgsockdir)
1061 os.mkdir(self._chgsockdir)
1058
1062
1059 def run(self, result):
1063 def run(self, result):
1060 """Run this test and report results against a TestResult instance."""
1064 """Run this test and report results against a TestResult instance."""
1061 # This function is extremely similar to unittest.TestCase.run(). Once
1065 # This function is extremely similar to unittest.TestCase.run(). Once
1062 # we require Python 2.7 (or at least its version of unittest), this
1066 # we require Python 2.7 (or at least its version of unittest), this
1063 # function can largely go away.
1067 # function can largely go away.
1064 self._result = result
1068 self._result = result
1065 result.startTest(self)
1069 result.startTest(self)
1066 try:
1070 try:
1067 try:
1071 try:
1068 self.setUp()
1072 self.setUp()
1069 except (KeyboardInterrupt, SystemExit):
1073 except (KeyboardInterrupt, SystemExit):
1070 self._aborted = True
1074 self._aborted = True
1071 raise
1075 raise
1072 except Exception:
1076 except Exception:
1073 result.addError(self, sys.exc_info())
1077 result.addError(self, sys.exc_info())
1074 return
1078 return
1075
1079
1076 success = False
1080 success = False
1077 try:
1081 try:
1078 self.runTest()
1082 self.runTest()
1079 except KeyboardInterrupt:
1083 except KeyboardInterrupt:
1080 self._aborted = True
1084 self._aborted = True
1081 raise
1085 raise
1082 except unittest.SkipTest as e:
1086 except unittest.SkipTest as e:
1083 result.addSkip(self, str(e))
1087 result.addSkip(self, str(e))
1084 # The base class will have already counted this as a
1088 # The base class will have already counted this as a
1085 # test we "ran", but we want to exclude skipped tests
1089 # test we "ran", but we want to exclude skipped tests
1086 # from those we count towards those run.
1090 # from those we count towards those run.
1087 result.testsRun -= 1
1091 result.testsRun -= 1
1088 except self.failureException as e:
1092 except self.failureException as e:
1089 # This differs from unittest in that we don't capture
1093 # This differs from unittest in that we don't capture
1090 # the stack trace. This is for historical reasons and
1094 # the stack trace. This is for historical reasons and
1091 # this decision could be revisited in the future,
1095 # this decision could be revisited in the future,
1092 # especially for PythonTest instances.
1096 # especially for PythonTest instances.
1093 if result.addFailure(self, str(e)):
1097 if result.addFailure(self, str(e)):
1094 success = True
1098 success = True
1095 except Exception:
1099 except Exception:
1096 result.addError(self, sys.exc_info())
1100 result.addError(self, sys.exc_info())
1097 else:
1101 else:
1098 success = True
1102 success = True
1099
1103
1100 try:
1104 try:
1101 self.tearDown()
1105 self.tearDown()
1102 except (KeyboardInterrupt, SystemExit):
1106 except (KeyboardInterrupt, SystemExit):
1103 self._aborted = True
1107 self._aborted = True
1104 raise
1108 raise
1105 except Exception:
1109 except Exception:
1106 result.addError(self, sys.exc_info())
1110 result.addError(self, sys.exc_info())
1107 success = False
1111 success = False
1108
1112
1109 if success:
1113 if success:
1110 result.addSuccess(self)
1114 result.addSuccess(self)
1111 finally:
1115 finally:
1112 result.stopTest(self, interrupted=self._aborted)
1116 result.stopTest(self, interrupted=self._aborted)
1113
1117
1114 def runTest(self):
1118 def runTest(self):
1115 """Run this test instance.
1119 """Run this test instance.
1116
1120
1117 This will return a tuple describing the result of the test.
1121 This will return a tuple describing the result of the test.
1118 """
1122 """
1119 env = self._getenv()
1123 env = self._getenv()
1120 self._genrestoreenv(env)
1124 self._genrestoreenv(env)
1121 self._daemonpids.append(env['DAEMON_PIDS'])
1125 self._daemonpids.append(env['DAEMON_PIDS'])
1122 self._createhgrc(env['HGRCPATH'])
1126 self._createhgrc(env['HGRCPATH'])
1123
1127
1124 vlog('# Test', self.name)
1128 vlog('# Test', self.name)
1125
1129
1126 ret, out = self._run(env)
1130 ret, out = self._run(env)
1127 self._finished = True
1131 self._finished = True
1128 self._ret = ret
1132 self._ret = ret
1129 self._out = out
1133 self._out = out
1130
1134
1131 def describe(ret):
1135 def describe(ret):
1132 if ret < 0:
1136 if ret < 0:
1133 return 'killed by signal: %d' % -ret
1137 return 'killed by signal: %d' % -ret
1134 return 'returned error code %d' % ret
1138 return 'returned error code %d' % ret
1135
1139
1136 self._skipped = False
1140 self._skipped = False
1137
1141
1138 if ret == self.SKIPPED_STATUS:
1142 if ret == self.SKIPPED_STATUS:
1139 if out is None: # Debug mode, nothing to parse.
1143 if out is None: # Debug mode, nothing to parse.
1140 missing = ['unknown']
1144 missing = ['unknown']
1141 failed = None
1145 failed = None
1142 else:
1146 else:
1143 missing, failed = TTest.parsehghaveoutput(out)
1147 missing, failed = TTest.parsehghaveoutput(out)
1144
1148
1145 if not missing:
1149 if not missing:
1146 missing = ['skipped']
1150 missing = ['skipped']
1147
1151
1148 if failed:
1152 if failed:
1149 self.fail('hg have failed checking for %s' % failed[-1])
1153 self.fail('hg have failed checking for %s' % failed[-1])
1150 else:
1154 else:
1151 self._skipped = True
1155 self._skipped = True
1152 raise unittest.SkipTest(missing[-1])
1156 raise unittest.SkipTest(missing[-1])
1153 elif ret == 'timeout':
1157 elif ret == 'timeout':
1154 self.fail('timed out')
1158 self.fail('timed out')
1155 elif ret is False:
1159 elif ret is False:
1156 self.fail('no result code from test')
1160 self.fail('no result code from test')
1157 elif out != self._refout:
1161 elif out != self._refout:
1158 # Diff generation may rely on written .err file.
1162 # Diff generation may rely on written .err file.
1159 if (
1163 if (
1160 (ret != 0 or out != self._refout)
1164 (ret != 0 or out != self._refout)
1161 and not self._skipped
1165 and not self._skipped
1162 and not self._debug
1166 and not self._debug
1163 ):
1167 ):
1164 with open(self.errpath, 'wb') as f:
1168 with open(self.errpath, 'wb') as f:
1165 for line in out:
1169 for line in out:
1166 f.write(line)
1170 f.write(line)
1167
1171
1168 # The result object handles diff calculation for us.
1172 # The result object handles diff calculation for us.
1169 with firstlock:
1173 with firstlock:
1170 if self._result.addOutputMismatch(self, ret, out, self._refout):
1174 if self._result.addOutputMismatch(self, ret, out, self._refout):
1171 # change was accepted, skip failing
1175 # change was accepted, skip failing
1172 return
1176 return
1173 if self._first:
1177 if self._first:
1174 global firsterror
1178 global firsterror
1175 firsterror = True
1179 firsterror = True
1176
1180
1177 if ret:
1181 if ret:
1178 msg = 'output changed and ' + describe(ret)
1182 msg = 'output changed and ' + describe(ret)
1179 else:
1183 else:
1180 msg = 'output changed'
1184 msg = 'output changed'
1181
1185
1182 self.fail(msg)
1186 self.fail(msg)
1183 elif ret:
1187 elif ret:
1184 self.fail(describe(ret))
1188 self.fail(describe(ret))
1185
1189
1186 def tearDown(self):
1190 def tearDown(self):
1187 """Tasks to perform after run()."""
1191 """Tasks to perform after run()."""
1188 for entry in self._daemonpids:
1192 for entry in self._daemonpids:
1189 killdaemons(entry)
1193 killdaemons(entry)
1190 self._daemonpids = []
1194 self._daemonpids = []
1191
1195
1192 if self._keeptmpdir:
1196 if self._keeptmpdir:
1193 log(
1197 log(
1194 '\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s'
1198 '\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s'
1195 % (_bytes2sys(self._testtmp), _bytes2sys(self._threadtmp),)
1199 % (_bytes2sys(self._testtmp), _bytes2sys(self._threadtmp),)
1196 )
1200 )
1197 else:
1201 else:
1198 try:
1202 try:
1199 shutil.rmtree(self._testtmp)
1203 shutil.rmtree(self._testtmp)
1200 except OSError:
1204 except OSError:
1201 # unreadable directory may be left in $TESTTMP; fix permission
1205 # unreadable directory may be left in $TESTTMP; fix permission
1202 # and try again
1206 # and try again
1203 makecleanable(self._testtmp)
1207 makecleanable(self._testtmp)
1204 shutil.rmtree(self._testtmp, True)
1208 shutil.rmtree(self._testtmp, True)
1205 shutil.rmtree(self._threadtmp, True)
1209 shutil.rmtree(self._threadtmp, True)
1206
1210
1207 if self._usechg:
1211 if self._usechg:
1208 # chgservers will stop automatically after they find the socket
1212 # chgservers will stop automatically after they find the socket
1209 # files are deleted
1213 # files are deleted
1210 shutil.rmtree(self._chgsockdir, True)
1214 shutil.rmtree(self._chgsockdir, True)
1211
1215
1212 if (
1216 if (
1213 (self._ret != 0 or self._out != self._refout)
1217 (self._ret != 0 or self._out != self._refout)
1214 and not self._skipped
1218 and not self._skipped
1215 and not self._debug
1219 and not self._debug
1216 and self._out
1220 and self._out
1217 ):
1221 ):
1218 with open(self.errpath, 'wb') as f:
1222 with open(self.errpath, 'wb') as f:
1219 for line in self._out:
1223 for line in self._out:
1220 f.write(line)
1224 f.write(line)
1221
1225
1222 vlog("# Ret was:", self._ret, '(%s)' % self.name)
1226 vlog("# Ret was:", self._ret, '(%s)' % self.name)
1223
1227
1224 def _run(self, env):
1228 def _run(self, env):
1225 # This should be implemented in child classes to run tests.
1229 # This should be implemented in child classes to run tests.
1226 raise unittest.SkipTest('unknown test type')
1230 raise unittest.SkipTest('unknown test type')
1227
1231
1228 def abort(self):
1232 def abort(self):
1229 """Terminate execution of this test."""
1233 """Terminate execution of this test."""
1230 self._aborted = True
1234 self._aborted = True
1231
1235
1232 def _portmap(self, i):
1236 def _portmap(self, i):
1233 offset = b'' if i == 0 else b'%d' % i
1237 offset = b'' if i == 0 else b'%d' % i
1234 return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset)
1238 return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset)
1235
1239
1236 def _getreplacements(self):
1240 def _getreplacements(self):
1237 """Obtain a mapping of text replacements to apply to test output.
1241 """Obtain a mapping of text replacements to apply to test output.
1238
1242
1239 Test output needs to be normalized so it can be compared to expected
1243 Test output needs to be normalized so it can be compared to expected
1240 output. This function defines how some of that normalization will
1244 output. This function defines how some of that normalization will
1241 occur.
1245 occur.
1242 """
1246 """
1243 r = [
1247 r = [
1244 # This list should be parallel to defineport in _getenv
1248 # This list should be parallel to defineport in _getenv
1245 self._portmap(0),
1249 self._portmap(0),
1246 self._portmap(1),
1250 self._portmap(1),
1247 self._portmap(2),
1251 self._portmap(2),
1248 (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'),
1252 (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'),
1249 (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'),
1253 (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'),
1250 ]
1254 ]
1251 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
1255 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
1252
1256
1253 replacementfile = os.path.join(self._testdir, b'common-pattern.py')
1257 replacementfile = os.path.join(self._testdir, b'common-pattern.py')
1254
1258
1255 if os.path.exists(replacementfile):
1259 if os.path.exists(replacementfile):
1256 data = {}
1260 data = {}
1257 with open(replacementfile, mode='rb') as source:
1261 with open(replacementfile, mode='rb') as source:
1258 # the intermediate 'compile' step help with debugging
1262 # the intermediate 'compile' step help with debugging
1259 code = compile(source.read(), replacementfile, 'exec')
1263 code = compile(source.read(), replacementfile, 'exec')
1260 exec(code, data)
1264 exec(code, data)
1261 for value in data.get('substitutions', ()):
1265 for value in data.get('substitutions', ()):
1262 if len(value) != 2:
1266 if len(value) != 2:
1263 msg = 'malformatted substitution in %s: %r'
1267 msg = 'malformatted substitution in %s: %r'
1264 msg %= (replacementfile, value)
1268 msg %= (replacementfile, value)
1265 raise ValueError(msg)
1269 raise ValueError(msg)
1266 r.append(value)
1270 r.append(value)
1267 return r
1271 return r
1268
1272
1269 def _escapepath(self, p):
1273 def _escapepath(self, p):
1270 if os.name == 'nt':
1274 if os.name == 'nt':
1271 return b''.join(
1275 return b''.join(
1272 c.isalpha()
1276 c.isalpha()
1273 and b'[%s%s]' % (c.lower(), c.upper())
1277 and b'[%s%s]' % (c.lower(), c.upper())
1274 or c in b'/\\'
1278 or c in b'/\\'
1275 and br'[/\\]'
1279 and br'[/\\]'
1276 or c.isdigit()
1280 or c.isdigit()
1277 and c
1281 and c
1278 or b'\\' + c
1282 or b'\\' + c
1279 for c in [p[i : i + 1] for i in range(len(p))]
1283 for c in [p[i : i + 1] for i in range(len(p))]
1280 )
1284 )
1281 else:
1285 else:
1282 return re.escape(p)
1286 return re.escape(p)
1283
1287
1284 def _localip(self):
1288 def _localip(self):
1285 if self._useipv6:
1289 if self._useipv6:
1286 return b'::1'
1290 return b'::1'
1287 else:
1291 else:
1288 return b'127.0.0.1'
1292 return b'127.0.0.1'
1289
1293
1290 def _genrestoreenv(self, testenv):
1294 def _genrestoreenv(self, testenv):
1291 """Generate a script that can be used by tests to restore the original
1295 """Generate a script that can be used by tests to restore the original
1292 environment."""
1296 environment."""
1293 # Put the restoreenv script inside self._threadtmp
1297 # Put the restoreenv script inside self._threadtmp
1294 scriptpath = os.path.join(self._threadtmp, b'restoreenv.sh')
1298 scriptpath = os.path.join(self._threadtmp, b'restoreenv.sh')
1295 testenv['HGTEST_RESTOREENV'] = _bytes2sys(scriptpath)
1299 testenv['HGTEST_RESTOREENV'] = _bytes2sys(scriptpath)
1296
1300
1297 # Only restore environment variable names that the shell allows
1301 # Only restore environment variable names that the shell allows
1298 # us to export.
1302 # us to export.
1299 name_regex = re.compile('^[a-zA-Z][a-zA-Z0-9_]*$')
1303 name_regex = re.compile('^[a-zA-Z][a-zA-Z0-9_]*$')
1300
1304
1301 # Do not restore these variables; otherwise tests would fail.
1305 # Do not restore these variables; otherwise tests would fail.
1302 reqnames = {'PYTHON', 'TESTDIR', 'TESTTMP'}
1306 reqnames = {'PYTHON', 'TESTDIR', 'TESTTMP'}
1303
1307
1304 with open(scriptpath, 'w') as envf:
1308 with open(scriptpath, 'w') as envf:
1305 for name, value in origenviron.items():
1309 for name, value in origenviron.items():
1306 if not name_regex.match(name):
1310 if not name_regex.match(name):
1307 # Skip environment variables with unusual names not
1311 # Skip environment variables with unusual names not
1308 # allowed by most shells.
1312 # allowed by most shells.
1309 continue
1313 continue
1310 if name in reqnames:
1314 if name in reqnames:
1311 continue
1315 continue
1312 envf.write('%s=%s\n' % (name, shellquote(value)))
1316 envf.write('%s=%s\n' % (name, shellquote(value)))
1313
1317
1314 for name in testenv:
1318 for name in testenv:
1315 if name in origenviron or name in reqnames:
1319 if name in origenviron or name in reqnames:
1316 continue
1320 continue
1317 envf.write('unset %s\n' % (name,))
1321 envf.write('unset %s\n' % (name,))
1318
1322
1319 def _getenv(self):
1323 def _getenv(self):
1320 """Obtain environment variables to use during test execution."""
1324 """Obtain environment variables to use during test execution."""
1321
1325
1322 def defineport(i):
1326 def defineport(i):
1323 offset = '' if i == 0 else '%s' % i
1327 offset = '' if i == 0 else '%s' % i
1324 env["HGPORT%s" % offset] = '%s' % (self._startport + i)
1328 env["HGPORT%s" % offset] = '%s' % (self._startport + i)
1325
1329
1326 env = os.environ.copy()
1330 env = os.environ.copy()
1327 env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or ''
1331 env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or ''
1328 env['HGEMITWARNINGS'] = '1'
1332 env['HGEMITWARNINGS'] = '1'
1329 env['TESTTMP'] = _bytes2sys(self._testtmp)
1333 env['TESTTMP'] = _bytes2sys(self._testtmp)
1330 env['TESTNAME'] = self.name
1334 env['TESTNAME'] = self.name
1331 env['HOME'] = _bytes2sys(self._testtmp)
1335 env['HOME'] = _bytes2sys(self._testtmp)
1332 formated_timeout = _bytes2sys(b"%d" % default_defaults['timeout'][1])
1336 formated_timeout = _bytes2sys(b"%d" % default_defaults['timeout'][1])
1333 env['HGTEST_TIMEOUT_DEFAULT'] = formated_timeout
1337 env['HGTEST_TIMEOUT_DEFAULT'] = formated_timeout
1334 env['HGTEST_TIMEOUT'] = _bytes2sys(b"%d" % self._timeout)
1338 env['HGTEST_TIMEOUT'] = _bytes2sys(b"%d" % self._timeout)
1335 # This number should match portneeded in _getport
1339 # This number should match portneeded in _getport
1336 for port in xrange(3):
1340 for port in xrange(3):
1337 # This list should be parallel to _portmap in _getreplacements
1341 # This list should be parallel to _portmap in _getreplacements
1338 defineport(port)
1342 defineport(port)
1339 env["HGRCPATH"] = _bytes2sys(os.path.join(self._threadtmp, b'.hgrc'))
1343 env["HGRCPATH"] = _bytes2sys(os.path.join(self._threadtmp, b'.hgrc'))
1340 env["DAEMON_PIDS"] = _bytes2sys(
1344 env["DAEMON_PIDS"] = _bytes2sys(
1341 os.path.join(self._threadtmp, b'daemon.pids')
1345 os.path.join(self._threadtmp, b'daemon.pids')
1342 )
1346 )
1343 env["HGEDITOR"] = (
1347 env["HGEDITOR"] = (
1344 '"' + sysexecutable + '"' + ' -c "import sys; sys.exit(0)"'
1348 '"' + sysexecutable + '"' + ' -c "import sys; sys.exit(0)"'
1345 )
1349 )
1346 env["HGUSER"] = "test"
1350 env["HGUSER"] = "test"
1347 env["HGENCODING"] = "ascii"
1351 env["HGENCODING"] = "ascii"
1348 env["HGENCODINGMODE"] = "strict"
1352 env["HGENCODINGMODE"] = "strict"
1349 env["HGHOSTNAME"] = "test-hostname"
1353 env["HGHOSTNAME"] = "test-hostname"
1350 env['HGIPV6'] = str(int(self._useipv6))
1354 env['HGIPV6'] = str(int(self._useipv6))
1351 # See contrib/catapipe.py for how to use this functionality.
1355 # See contrib/catapipe.py for how to use this functionality.
1352 if 'HGTESTCATAPULTSERVERPIPE' not in env:
1356 if 'HGTESTCATAPULTSERVERPIPE' not in env:
1353 # If we don't have HGTESTCATAPULTSERVERPIPE explicitly set, pull the
1357 # If we don't have HGTESTCATAPULTSERVERPIPE explicitly set, pull the
1354 # non-test one in as a default, otherwise set to devnull
1358 # non-test one in as a default, otherwise set to devnull
1355 env['HGTESTCATAPULTSERVERPIPE'] = env.get(
1359 env['HGTESTCATAPULTSERVERPIPE'] = env.get(
1356 'HGCATAPULTSERVERPIPE', os.devnull
1360 'HGCATAPULTSERVERPIPE', os.devnull
1357 )
1361 )
1358
1362
1359 extraextensions = []
1363 extraextensions = []
1360 for opt in self._extraconfigopts:
1364 for opt in self._extraconfigopts:
1361 section, key = _sys2bytes(opt).split(b'.', 1)
1365 section, key = _sys2bytes(opt).split(b'.', 1)
1362 if section != 'extensions':
1366 if section != 'extensions':
1363 continue
1367 continue
1364 name = key.split(b'=', 1)[0]
1368 name = key.split(b'=', 1)[0]
1365 extraextensions.append(name)
1369 extraextensions.append(name)
1366
1370
1367 if extraextensions:
1371 if extraextensions:
1368 env['HGTESTEXTRAEXTENSIONS'] = b' '.join(extraextensions)
1372 env['HGTESTEXTRAEXTENSIONS'] = b' '.join(extraextensions)
1369
1373
1370 # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw
1374 # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw
1371 # IP addresses.
1375 # IP addresses.
1372 env['LOCALIP'] = _bytes2sys(self._localip())
1376 env['LOCALIP'] = _bytes2sys(self._localip())
1373
1377
1374 # This has the same effect as Py_LegacyWindowsStdioFlag in exewrapper.c,
1378 # This has the same effect as Py_LegacyWindowsStdioFlag in exewrapper.c,
1375 # but this is needed for testing python instances like dummyssh,
1379 # but this is needed for testing python instances like dummyssh,
1376 # dummysmtpd.py, and dumbhttp.py.
1380 # dummysmtpd.py, and dumbhttp.py.
1377 if PYTHON3 and os.name == 'nt':
1381 if PYTHON3 and os.name == 'nt':
1378 env['PYTHONLEGACYWINDOWSSTDIO'] = '1'
1382 env['PYTHONLEGACYWINDOWSSTDIO'] = '1'
1379
1383
1380 # Modified HOME in test environment can confuse Rust tools. So set
1384 # Modified HOME in test environment can confuse Rust tools. So set
1381 # CARGO_HOME and RUSTUP_HOME automatically if a Rust toolchain is
1385 # CARGO_HOME and RUSTUP_HOME automatically if a Rust toolchain is
1382 # present and these variables aren't already defined.
1386 # present and these variables aren't already defined.
1383 cargo_home_path = os.path.expanduser('~/.cargo')
1387 cargo_home_path = os.path.expanduser('~/.cargo')
1384 rustup_home_path = os.path.expanduser('~/.rustup')
1388 rustup_home_path = os.path.expanduser('~/.rustup')
1385
1389
1386 if os.path.exists(cargo_home_path) and b'CARGO_HOME' not in osenvironb:
1390 if os.path.exists(cargo_home_path) and b'CARGO_HOME' not in osenvironb:
1387 env['CARGO_HOME'] = cargo_home_path
1391 env['CARGO_HOME'] = cargo_home_path
1388 if (
1392 if (
1389 os.path.exists(rustup_home_path)
1393 os.path.exists(rustup_home_path)
1390 and b'RUSTUP_HOME' not in osenvironb
1394 and b'RUSTUP_HOME' not in osenvironb
1391 ):
1395 ):
1392 env['RUSTUP_HOME'] = rustup_home_path
1396 env['RUSTUP_HOME'] = rustup_home_path
1393
1397
1394 # Reset some environment variables to well-known values so that
1398 # Reset some environment variables to well-known values so that
1395 # the tests produce repeatable output.
1399 # the tests produce repeatable output.
1396 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
1400 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
1397 env['TZ'] = 'GMT'
1401 env['TZ'] = 'GMT'
1398 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1402 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1399 env['COLUMNS'] = '80'
1403 env['COLUMNS'] = '80'
1400 env['TERM'] = 'xterm'
1404 env['TERM'] = 'xterm'
1401
1405
1402 dropped = [
1406 dropped = [
1403 'CDPATH',
1407 'CDPATH',
1404 'CHGDEBUG',
1408 'CHGDEBUG',
1405 'EDITOR',
1409 'EDITOR',
1406 'GREP_OPTIONS',
1410 'GREP_OPTIONS',
1407 'HG',
1411 'HG',
1408 'HGMERGE',
1412 'HGMERGE',
1409 'HGPLAIN',
1413 'HGPLAIN',
1410 'HGPLAINEXCEPT',
1414 'HGPLAINEXCEPT',
1411 'HGPROF',
1415 'HGPROF',
1412 'http_proxy',
1416 'http_proxy',
1413 'no_proxy',
1417 'no_proxy',
1414 'NO_PROXY',
1418 'NO_PROXY',
1415 'PAGER',
1419 'PAGER',
1416 'VISUAL',
1420 'VISUAL',
1417 ]
1421 ]
1418
1422
1419 for k in dropped:
1423 for k in dropped:
1420 if k in env:
1424 if k in env:
1421 del env[k]
1425 del env[k]
1422
1426
1423 # unset env related to hooks
1427 # unset env related to hooks
1424 for k in list(env):
1428 for k in list(env):
1425 if k.startswith('HG_'):
1429 if k.startswith('HG_'):
1426 del env[k]
1430 del env[k]
1427
1431
1428 if self._usechg:
1432 if self._usechg:
1429 env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server')
1433 env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server')
1430 if self._chgdebug:
1434 if self._chgdebug:
1431 env['CHGDEBUG'] = 'true'
1435 env['CHGDEBUG'] = 'true'
1432
1436
1433 return env
1437 return env
1434
1438
1435 def _createhgrc(self, path):
1439 def _createhgrc(self, path):
1436 """Create an hgrc file for this test."""
1440 """Create an hgrc file for this test."""
1437 with open(path, 'wb') as hgrc:
1441 with open(path, 'wb') as hgrc:
1438 hgrc.write(b'[ui]\n')
1442 hgrc.write(b'[ui]\n')
1439 hgrc.write(b'slash = True\n')
1443 hgrc.write(b'slash = True\n')
1440 hgrc.write(b'interactive = False\n')
1444 hgrc.write(b'interactive = False\n')
1441 hgrc.write(b'merge = internal:merge\n')
1445 hgrc.write(b'merge = internal:merge\n')
1442 hgrc.write(b'mergemarkers = detailed\n')
1446 hgrc.write(b'mergemarkers = detailed\n')
1443 hgrc.write(b'promptecho = True\n')
1447 hgrc.write(b'promptecho = True\n')
1444 hgrc.write(b'[defaults]\n')
1448 hgrc.write(b'[defaults]\n')
1445 hgrc.write(b'[devel]\n')
1449 hgrc.write(b'[devel]\n')
1446 hgrc.write(b'all-warnings = true\n')
1450 hgrc.write(b'all-warnings = true\n')
1447 hgrc.write(b'default-date = 0 0\n')
1451 hgrc.write(b'default-date = 0 0\n')
1448 hgrc.write(b'[largefiles]\n')
1452 hgrc.write(b'[largefiles]\n')
1449 hgrc.write(
1453 hgrc.write(
1450 b'usercache = %s\n'
1454 b'usercache = %s\n'
1451 % (os.path.join(self._testtmp, b'.cache/largefiles'))
1455 % (os.path.join(self._testtmp, b'.cache/largefiles'))
1452 )
1456 )
1453 hgrc.write(b'[lfs]\n')
1457 hgrc.write(b'[lfs]\n')
1454 hgrc.write(
1458 hgrc.write(
1455 b'usercache = %s\n'
1459 b'usercache = %s\n'
1456 % (os.path.join(self._testtmp, b'.cache/lfs'))
1460 % (os.path.join(self._testtmp, b'.cache/lfs'))
1457 )
1461 )
1458 hgrc.write(b'[web]\n')
1462 hgrc.write(b'[web]\n')
1459 hgrc.write(b'address = localhost\n')
1463 hgrc.write(b'address = localhost\n')
1460 hgrc.write(b'ipv6 = %r\n' % self._useipv6)
1464 hgrc.write(b'ipv6 = %r\n' % self._useipv6)
1461 hgrc.write(b'server-header = testing stub value\n')
1465 hgrc.write(b'server-header = testing stub value\n')
1462
1466
1463 for opt in self._extraconfigopts:
1467 for opt in self._extraconfigopts:
1464 section, key = _sys2bytes(opt).split(b'.', 1)
1468 section, key = _sys2bytes(opt).split(b'.', 1)
1465 assert b'=' in key, (
1469 assert b'=' in key, (
1466 'extra config opt %s must ' 'have an = for assignment' % opt
1470 'extra config opt %s must ' 'have an = for assignment' % opt
1467 )
1471 )
1468 hgrc.write(b'[%s]\n%s\n' % (section, key))
1472 hgrc.write(b'[%s]\n%s\n' % (section, key))
1469
1473
1470 def fail(self, msg):
1474 def fail(self, msg):
1471 # unittest differentiates between errored and failed.
1475 # unittest differentiates between errored and failed.
1472 # Failed is denoted by AssertionError (by default at least).
1476 # Failed is denoted by AssertionError (by default at least).
1473 raise AssertionError(msg)
1477 raise AssertionError(msg)
1474
1478
1475 def _runcommand(self, cmd, env, normalizenewlines=False):
1479 def _runcommand(self, cmd, env, normalizenewlines=False):
1476 """Run command in a sub-process, capturing the output (stdout and
1480 """Run command in a sub-process, capturing the output (stdout and
1477 stderr).
1481 stderr).
1478
1482
1479 Return a tuple (exitcode, output). output is None in debug mode.
1483 Return a tuple (exitcode, output). output is None in debug mode.
1480 """
1484 """
1481 if self._debug:
1485 if self._debug:
1482 proc = subprocess.Popen(
1486 proc = subprocess.Popen(
1483 _bytes2sys(cmd),
1487 _bytes2sys(cmd),
1484 shell=True,
1488 shell=True,
1485 cwd=_bytes2sys(self._testtmp),
1489 cwd=_bytes2sys(self._testtmp),
1486 env=env,
1490 env=env,
1487 )
1491 )
1488 ret = proc.wait()
1492 ret = proc.wait()
1489 return (ret, None)
1493 return (ret, None)
1490
1494
1491 proc = Popen4(cmd, self._testtmp, self._timeout, env)
1495 proc = Popen4(cmd, self._testtmp, self._timeout, env)
1492
1496
1493 def cleanup():
1497 def cleanup():
1494 terminate(proc)
1498 terminate(proc)
1495 ret = proc.wait()
1499 ret = proc.wait()
1496 if ret == 0:
1500 if ret == 0:
1497 ret = signal.SIGTERM << 8
1501 ret = signal.SIGTERM << 8
1498 killdaemons(env['DAEMON_PIDS'])
1502 killdaemons(env['DAEMON_PIDS'])
1499 return ret
1503 return ret
1500
1504
1501 proc.tochild.close()
1505 proc.tochild.close()
1502
1506
1503 try:
1507 try:
1504 output = proc.fromchild.read()
1508 output = proc.fromchild.read()
1505 except KeyboardInterrupt:
1509 except KeyboardInterrupt:
1506 vlog('# Handling keyboard interrupt')
1510 vlog('# Handling keyboard interrupt')
1507 cleanup()
1511 cleanup()
1508 raise
1512 raise
1509
1513
1510 ret = proc.wait()
1514 ret = proc.wait()
1511 if wifexited(ret):
1515 if wifexited(ret):
1512 ret = os.WEXITSTATUS(ret)
1516 ret = os.WEXITSTATUS(ret)
1513
1517
1514 if proc.timeout:
1518 if proc.timeout:
1515 ret = 'timeout'
1519 ret = 'timeout'
1516
1520
1517 if ret:
1521 if ret:
1518 killdaemons(env['DAEMON_PIDS'])
1522 killdaemons(env['DAEMON_PIDS'])
1519
1523
1520 for s, r in self._getreplacements():
1524 for s, r in self._getreplacements():
1521 output = re.sub(s, r, output)
1525 output = re.sub(s, r, output)
1522
1526
1523 if normalizenewlines:
1527 if normalizenewlines:
1524 output = output.replace(b'\r\n', b'\n')
1528 output = output.replace(b'\r\n', b'\n')
1525
1529
1526 return ret, output.splitlines(True)
1530 return ret, output.splitlines(True)
1527
1531
1528
1532
1529 class PythonTest(Test):
1533 class PythonTest(Test):
1530 """A Python-based test."""
1534 """A Python-based test."""
1531
1535
1532 @property
1536 @property
1533 def refpath(self):
1537 def refpath(self):
1534 return os.path.join(self._testdir, b'%s.out' % self.bname)
1538 return os.path.join(self._testdir, b'%s.out' % self.bname)
1535
1539
1536 def _run(self, env):
1540 def _run(self, env):
1537 # Quote the python(3) executable for Windows
1541 # Quote the python(3) executable for Windows
1538 cmd = b'"%s" "%s"' % (PYTHON, self.path)
1542 cmd = b'"%s" "%s"' % (PYTHON, self.path)
1539 vlog("# Running", cmd.decode("utf-8"))
1543 vlog("# Running", cmd.decode("utf-8"))
1540 normalizenewlines = os.name == 'nt'
1544 normalizenewlines = os.name == 'nt'
1541 result = self._runcommand(cmd, env, normalizenewlines=normalizenewlines)
1545 result = self._runcommand(cmd, env, normalizenewlines=normalizenewlines)
1542 if self._aborted:
1546 if self._aborted:
1543 raise KeyboardInterrupt()
1547 raise KeyboardInterrupt()
1544
1548
1545 return result
1549 return result
1546
1550
1547
1551
1548 # Some glob patterns apply only in some circumstances, so the script
1552 # Some glob patterns apply only in some circumstances, so the script
1549 # might want to remove (glob) annotations that otherwise should be
1553 # might want to remove (glob) annotations that otherwise should be
1550 # retained.
1554 # retained.
1551 checkcodeglobpats = [
1555 checkcodeglobpats = [
1552 # On Windows it looks like \ doesn't require a (glob), but we know
1556 # On Windows it looks like \ doesn't require a (glob), but we know
1553 # better.
1557 # better.
1554 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
1558 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
1555 re.compile(br'^moving \S+/.*[^)]$'),
1559 re.compile(br'^moving \S+/.*[^)]$'),
1556 re.compile(br'^pulling from \$TESTTMP/.*[^)]$'),
1560 re.compile(br'^pulling from \$TESTTMP/.*[^)]$'),
1557 # Not all platforms have 127.0.0.1 as loopback (though most do),
1561 # Not all platforms have 127.0.0.1 as loopback (though most do),
1558 # so we always glob that too.
1562 # so we always glob that too.
1559 re.compile(br'.*\$LOCALIP.*$'),
1563 re.compile(br'.*\$LOCALIP.*$'),
1560 ]
1564 ]
1561
1565
1562 bchr = chr
1566 bchr = chr
1563 if PYTHON3:
1567 if PYTHON3:
1564 bchr = lambda x: bytes([x])
1568 bchr = lambda x: bytes([x])
1565
1569
1566 WARN_UNDEFINED = 1
1570 WARN_UNDEFINED = 1
1567 WARN_YES = 2
1571 WARN_YES = 2
1568 WARN_NO = 3
1572 WARN_NO = 3
1569
1573
1570 MARK_OPTIONAL = b" (?)\n"
1574 MARK_OPTIONAL = b" (?)\n"
1571
1575
1572
1576
1573 def isoptional(line):
1577 def isoptional(line):
1574 return line.endswith(MARK_OPTIONAL)
1578 return line.endswith(MARK_OPTIONAL)
1575
1579
1576
1580
1577 class TTest(Test):
1581 class TTest(Test):
1578 """A "t test" is a test backed by a .t file."""
1582 """A "t test" is a test backed by a .t file."""
1579
1583
1580 SKIPPED_PREFIX = b'skipped: '
1584 SKIPPED_PREFIX = b'skipped: '
1581 FAILED_PREFIX = b'hghave check failed: '
1585 FAILED_PREFIX = b'hghave check failed: '
1582 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
1586 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
1583
1587
1584 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
1588 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
1585 ESCAPEMAP = {bchr(i): br'\x%02x' % i for i in range(256)}
1589 ESCAPEMAP = {bchr(i): br'\x%02x' % i for i in range(256)}
1586 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
1590 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
1587
1591
1588 def __init__(self, path, *args, **kwds):
1592 def __init__(self, path, *args, **kwds):
1589 # accept an extra "case" parameter
1593 # accept an extra "case" parameter
1590 case = kwds.pop('case', [])
1594 case = kwds.pop('case', [])
1591 self._case = case
1595 self._case = case
1592 self._allcases = {x for y in parsettestcases(path) for x in y}
1596 self._allcases = {x for y in parsettestcases(path) for x in y}
1593 super(TTest, self).__init__(path, *args, **kwds)
1597 super(TTest, self).__init__(path, *args, **kwds)
1594 if case:
1598 if case:
1595 casepath = b'#'.join(case)
1599 casepath = b'#'.join(case)
1596 self.name = '%s#%s' % (self.name, _bytes2sys(casepath))
1600 self.name = '%s#%s' % (self.name, _bytes2sys(casepath))
1597 self.errpath = b'%s#%s.err' % (self.errpath[:-4], casepath)
1601 self.errpath = b'%s#%s.err' % (self.errpath[:-4], casepath)
1598 self._tmpname += b'-%s' % casepath
1602 self._tmpname += b'-%s' % casepath
1599 self._have = {}
1603 self._have = {}
1600
1604
1601 @property
1605 @property
1602 def refpath(self):
1606 def refpath(self):
1603 return os.path.join(self._testdir, self.bname)
1607 return os.path.join(self._testdir, self.bname)
1604
1608
1605 def _run(self, env):
1609 def _run(self, env):
1606 with open(self.path, 'rb') as f:
1610 with open(self.path, 'rb') as f:
1607 lines = f.readlines()
1611 lines = f.readlines()
1608
1612
1609 # .t file is both reference output and the test input, keep reference
1613 # .t file is both reference output and the test input, keep reference
1610 # output updated with the the test input. This avoids some race
1614 # output updated with the the test input. This avoids some race
1611 # conditions where the reference output does not match the actual test.
1615 # conditions where the reference output does not match the actual test.
1612 if self._refout is not None:
1616 if self._refout is not None:
1613 self._refout = lines
1617 self._refout = lines
1614
1618
1615 salt, script, after, expected = self._parsetest(lines)
1619 salt, script, after, expected = self._parsetest(lines)
1616
1620
1617 # Write out the generated script.
1621 # Write out the generated script.
1618 fname = b'%s.sh' % self._testtmp
1622 fname = b'%s.sh' % self._testtmp
1619 with open(fname, 'wb') as f:
1623 with open(fname, 'wb') as f:
1620 for l in script:
1624 for l in script:
1621 f.write(l)
1625 f.write(l)
1622
1626
1623 cmd = b'%s "%s"' % (self._shell, fname)
1627 cmd = b'%s "%s"' % (self._shell, fname)
1624 vlog("# Running", cmd.decode("utf-8"))
1628 vlog("# Running", cmd.decode("utf-8"))
1625
1629
1626 exitcode, output = self._runcommand(cmd, env)
1630 exitcode, output = self._runcommand(cmd, env)
1627
1631
1628 if self._aborted:
1632 if self._aborted:
1629 raise KeyboardInterrupt()
1633 raise KeyboardInterrupt()
1630
1634
1631 # Do not merge output if skipped. Return hghave message instead.
1635 # Do not merge output if skipped. Return hghave message instead.
1632 # Similarly, with --debug, output is None.
1636 # Similarly, with --debug, output is None.
1633 if exitcode == self.SKIPPED_STATUS or output is None:
1637 if exitcode == self.SKIPPED_STATUS or output is None:
1634 return exitcode, output
1638 return exitcode, output
1635
1639
1636 return self._processoutput(exitcode, output, salt, after, expected)
1640 return self._processoutput(exitcode, output, salt, after, expected)
1637
1641
1638 def _hghave(self, reqs):
1642 def _hghave(self, reqs):
1639 allreqs = b' '.join(reqs)
1643 allreqs = b' '.join(reqs)
1640
1644
1641 self._detectslow(reqs)
1645 self._detectslow(reqs)
1642
1646
1643 if allreqs in self._have:
1647 if allreqs in self._have:
1644 return self._have.get(allreqs)
1648 return self._have.get(allreqs)
1645
1649
1646 # TODO do something smarter when all other uses of hghave are gone.
1650 # TODO do something smarter when all other uses of hghave are gone.
1647 runtestdir = osenvironb[b'RUNTESTDIR']
1651 runtestdir = osenvironb[b'RUNTESTDIR']
1648 tdir = runtestdir.replace(b'\\', b'/')
1652 tdir = runtestdir.replace(b'\\', b'/')
1649 proc = Popen4(
1653 proc = Popen4(
1650 b'%s -c "%s/hghave %s"' % (self._shell, tdir, allreqs),
1654 b'%s -c "%s/hghave %s"' % (self._shell, tdir, allreqs),
1651 self._testtmp,
1655 self._testtmp,
1652 0,
1656 0,
1653 self._getenv(),
1657 self._getenv(),
1654 )
1658 )
1655 stdout, stderr = proc.communicate()
1659 stdout, stderr = proc.communicate()
1656 ret = proc.wait()
1660 ret = proc.wait()
1657 if wifexited(ret):
1661 if wifexited(ret):
1658 ret = os.WEXITSTATUS(ret)
1662 ret = os.WEXITSTATUS(ret)
1659 if ret == 2:
1663 if ret == 2:
1660 print(stdout.decode('utf-8'))
1664 print(stdout.decode('utf-8'))
1661 sys.exit(1)
1665 sys.exit(1)
1662
1666
1663 if ret != 0:
1667 if ret != 0:
1664 self._have[allreqs] = (False, stdout)
1668 self._have[allreqs] = (False, stdout)
1665 return False, stdout
1669 return False, stdout
1666
1670
1667 self._have[allreqs] = (True, None)
1671 self._have[allreqs] = (True, None)
1668 return True, None
1672 return True, None
1669
1673
1670 def _detectslow(self, reqs):
1674 def _detectslow(self, reqs):
1671 """update the timeout of slow test when appropriate"""
1675 """update the timeout of slow test when appropriate"""
1672 if b'slow' in reqs:
1676 if b'slow' in reqs:
1673 self._timeout = self._slowtimeout
1677 self._timeout = self._slowtimeout
1674
1678
1675 def _iftest(self, args):
1679 def _iftest(self, args):
1676 # implements "#if"
1680 # implements "#if"
1677 reqs = []
1681 reqs = []
1678 for arg in args:
1682 for arg in args:
1679 if arg.startswith(b'no-') and arg[3:] in self._allcases:
1683 if arg.startswith(b'no-') and arg[3:] in self._allcases:
1680 if arg[3:] in self._case:
1684 if arg[3:] in self._case:
1681 return False
1685 return False
1682 elif arg in self._allcases:
1686 elif arg in self._allcases:
1683 if arg not in self._case:
1687 if arg not in self._case:
1684 return False
1688 return False
1685 else:
1689 else:
1686 reqs.append(arg)
1690 reqs.append(arg)
1687 self._detectslow(reqs)
1691 self._detectslow(reqs)
1688 return self._hghave(reqs)[0]
1692 return self._hghave(reqs)[0]
1689
1693
1690 def _parsetest(self, lines):
1694 def _parsetest(self, lines):
1691 # We generate a shell script which outputs unique markers to line
1695 # We generate a shell script which outputs unique markers to line
1692 # up script results with our source. These markers include input
1696 # up script results with our source. These markers include input
1693 # line number and the last return code.
1697 # line number and the last return code.
1694 salt = b"SALT%d" % time.time()
1698 salt = b"SALT%d" % time.time()
1695
1699
1696 def addsalt(line, inpython):
1700 def addsalt(line, inpython):
1697 if inpython:
1701 if inpython:
1698 script.append(b'%s %d 0\n' % (salt, line))
1702 script.append(b'%s %d 0\n' % (salt, line))
1699 else:
1703 else:
1700 script.append(b'echo %s %d $?\n' % (salt, line))
1704 script.append(b'echo %s %d $?\n' % (salt, line))
1701
1705
1702 activetrace = []
1706 activetrace = []
1703 session = str(uuid.uuid4())
1707 session = str(uuid.uuid4())
1704 if PYTHON3:
1708 if PYTHON3:
1705 session = session.encode('ascii')
1709 session = session.encode('ascii')
1706 hgcatapult = os.getenv('HGTESTCATAPULTSERVERPIPE') or os.getenv(
1710 hgcatapult = os.getenv('HGTESTCATAPULTSERVERPIPE') or os.getenv(
1707 'HGCATAPULTSERVERPIPE'
1711 'HGCATAPULTSERVERPIPE'
1708 )
1712 )
1709
1713
1710 def toggletrace(cmd=None):
1714 def toggletrace(cmd=None):
1711 if not hgcatapult or hgcatapult == os.devnull:
1715 if not hgcatapult or hgcatapult == os.devnull:
1712 return
1716 return
1713
1717
1714 if activetrace:
1718 if activetrace:
1715 script.append(
1719 script.append(
1716 b'echo END %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1720 b'echo END %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1717 % (session, activetrace[0])
1721 % (session, activetrace[0])
1718 )
1722 )
1719 if cmd is None:
1723 if cmd is None:
1720 return
1724 return
1721
1725
1722 if isinstance(cmd, str):
1726 if isinstance(cmd, str):
1723 quoted = shellquote(cmd.strip())
1727 quoted = shellquote(cmd.strip())
1724 else:
1728 else:
1725 quoted = shellquote(cmd.strip().decode('utf8')).encode('utf8')
1729 quoted = shellquote(cmd.strip().decode('utf8')).encode('utf8')
1726 quoted = quoted.replace(b'\\', b'\\\\')
1730 quoted = quoted.replace(b'\\', b'\\\\')
1727 script.append(
1731 script.append(
1728 b'echo START %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1732 b'echo START %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1729 % (session, quoted)
1733 % (session, quoted)
1730 )
1734 )
1731 activetrace[0:] = [quoted]
1735 activetrace[0:] = [quoted]
1732
1736
1733 script = []
1737 script = []
1734
1738
1735 # After we run the shell script, we re-unify the script output
1739 # After we run the shell script, we re-unify the script output
1736 # with non-active parts of the source, with synchronization by our
1740 # with non-active parts of the source, with synchronization by our
1737 # SALT line number markers. The after table contains the non-active
1741 # SALT line number markers. The after table contains the non-active
1738 # components, ordered by line number.
1742 # components, ordered by line number.
1739 after = {}
1743 after = {}
1740
1744
1741 # Expected shell script output.
1745 # Expected shell script output.
1742 expected = {}
1746 expected = {}
1743
1747
1744 pos = prepos = -1
1748 pos = prepos = -1
1745
1749
1746 # True or False when in a true or false conditional section
1750 # True or False when in a true or false conditional section
1747 skipping = None
1751 skipping = None
1748
1752
1749 # We keep track of whether or not we're in a Python block so we
1753 # We keep track of whether or not we're in a Python block so we
1750 # can generate the surrounding doctest magic.
1754 # can generate the surrounding doctest magic.
1751 inpython = False
1755 inpython = False
1752
1756
1753 if self._debug:
1757 if self._debug:
1754 script.append(b'set -x\n')
1758 script.append(b'set -x\n')
1755 if self._hgcommand != b'hg':
1759 if self._hgcommand != b'hg':
1756 script.append(b'alias hg="%s"\n' % self._hgcommand)
1760 script.append(b'alias hg="%s"\n' % self._hgcommand)
1761 if self._rhgcommand != b'rhg':
1762 script.append(b'alias rhg="%s"\n' % self._rhgcommand)
1757 if os.getenv('MSYSTEM'):
1763 if os.getenv('MSYSTEM'):
1758 script.append(b'alias pwd="pwd -W"\n')
1764 script.append(b'alias pwd="pwd -W"\n')
1759
1765
1760 if hgcatapult and hgcatapult != os.devnull:
1766 if hgcatapult and hgcatapult != os.devnull:
1761 if PYTHON3:
1767 if PYTHON3:
1762 hgcatapult = hgcatapult.encode('utf8')
1768 hgcatapult = hgcatapult.encode('utf8')
1763 cataname = self.name.encode('utf8')
1769 cataname = self.name.encode('utf8')
1764 else:
1770 else:
1765 cataname = self.name
1771 cataname = self.name
1766
1772
1767 # Kludge: use a while loop to keep the pipe from getting
1773 # Kludge: use a while loop to keep the pipe from getting
1768 # closed by our echo commands. The still-running file gets
1774 # closed by our echo commands. The still-running file gets
1769 # reaped at the end of the script, which causes the while
1775 # reaped at the end of the script, which causes the while
1770 # loop to exit and closes the pipe. Sigh.
1776 # loop to exit and closes the pipe. Sigh.
1771 script.append(
1777 script.append(
1772 b'rtendtracing() {\n'
1778 b'rtendtracing() {\n'
1773 b' echo END %(session)s %(name)s >> %(catapult)s\n'
1779 b' echo END %(session)s %(name)s >> %(catapult)s\n'
1774 b' rm -f "$TESTTMP/.still-running"\n'
1780 b' rm -f "$TESTTMP/.still-running"\n'
1775 b'}\n'
1781 b'}\n'
1776 b'trap "rtendtracing" 0\n'
1782 b'trap "rtendtracing" 0\n'
1777 b'touch "$TESTTMP/.still-running"\n'
1783 b'touch "$TESTTMP/.still-running"\n'
1778 b'while [ -f "$TESTTMP/.still-running" ]; do sleep 1; done '
1784 b'while [ -f "$TESTTMP/.still-running" ]; do sleep 1; done '
1779 b'> %(catapult)s &\n'
1785 b'> %(catapult)s &\n'
1780 b'HGCATAPULTSESSION=%(session)s ; export HGCATAPULTSESSION\n'
1786 b'HGCATAPULTSESSION=%(session)s ; export HGCATAPULTSESSION\n'
1781 b'echo START %(session)s %(name)s >> %(catapult)s\n'
1787 b'echo START %(session)s %(name)s >> %(catapult)s\n'
1782 % {
1788 % {
1783 b'name': cataname,
1789 b'name': cataname,
1784 b'session': session,
1790 b'session': session,
1785 b'catapult': hgcatapult,
1791 b'catapult': hgcatapult,
1786 }
1792 }
1787 )
1793 )
1788
1794
1789 if self._case:
1795 if self._case:
1790 casestr = b'#'.join(self._case)
1796 casestr = b'#'.join(self._case)
1791 if isinstance(casestr, str):
1797 if isinstance(casestr, str):
1792 quoted = shellquote(casestr)
1798 quoted = shellquote(casestr)
1793 else:
1799 else:
1794 quoted = shellquote(casestr.decode('utf8')).encode('utf8')
1800 quoted = shellquote(casestr.decode('utf8')).encode('utf8')
1795 script.append(b'TESTCASE=%s\n' % quoted)
1801 script.append(b'TESTCASE=%s\n' % quoted)
1796 script.append(b'export TESTCASE\n')
1802 script.append(b'export TESTCASE\n')
1797
1803
1798 n = 0
1804 n = 0
1799 for n, l in enumerate(lines):
1805 for n, l in enumerate(lines):
1800 if not l.endswith(b'\n'):
1806 if not l.endswith(b'\n'):
1801 l += b'\n'
1807 l += b'\n'
1802 if l.startswith(b'#require'):
1808 if l.startswith(b'#require'):
1803 lsplit = l.split()
1809 lsplit = l.split()
1804 if len(lsplit) < 2 or lsplit[0] != b'#require':
1810 if len(lsplit) < 2 or lsplit[0] != b'#require':
1805 after.setdefault(pos, []).append(
1811 after.setdefault(pos, []).append(
1806 b' !!! invalid #require\n'
1812 b' !!! invalid #require\n'
1807 )
1813 )
1808 if not skipping:
1814 if not skipping:
1809 haveresult, message = self._hghave(lsplit[1:])
1815 haveresult, message = self._hghave(lsplit[1:])
1810 if not haveresult:
1816 if not haveresult:
1811 script = [b'echo "%s"\nexit 80\n' % message]
1817 script = [b'echo "%s"\nexit 80\n' % message]
1812 break
1818 break
1813 after.setdefault(pos, []).append(l)
1819 after.setdefault(pos, []).append(l)
1814 elif l.startswith(b'#if'):
1820 elif l.startswith(b'#if'):
1815 lsplit = l.split()
1821 lsplit = l.split()
1816 if len(lsplit) < 2 or lsplit[0] != b'#if':
1822 if len(lsplit) < 2 or lsplit[0] != b'#if':
1817 after.setdefault(pos, []).append(b' !!! invalid #if\n')
1823 after.setdefault(pos, []).append(b' !!! invalid #if\n')
1818 if skipping is not None:
1824 if skipping is not None:
1819 after.setdefault(pos, []).append(b' !!! nested #if\n')
1825 after.setdefault(pos, []).append(b' !!! nested #if\n')
1820 skipping = not self._iftest(lsplit[1:])
1826 skipping = not self._iftest(lsplit[1:])
1821 after.setdefault(pos, []).append(l)
1827 after.setdefault(pos, []).append(l)
1822 elif l.startswith(b'#else'):
1828 elif l.startswith(b'#else'):
1823 if skipping is None:
1829 if skipping is None:
1824 after.setdefault(pos, []).append(b' !!! missing #if\n')
1830 after.setdefault(pos, []).append(b' !!! missing #if\n')
1825 skipping = not skipping
1831 skipping = not skipping
1826 after.setdefault(pos, []).append(l)
1832 after.setdefault(pos, []).append(l)
1827 elif l.startswith(b'#endif'):
1833 elif l.startswith(b'#endif'):
1828 if skipping is None:
1834 if skipping is None:
1829 after.setdefault(pos, []).append(b' !!! missing #if\n')
1835 after.setdefault(pos, []).append(b' !!! missing #if\n')
1830 skipping = None
1836 skipping = None
1831 after.setdefault(pos, []).append(l)
1837 after.setdefault(pos, []).append(l)
1832 elif skipping:
1838 elif skipping:
1833 after.setdefault(pos, []).append(l)
1839 after.setdefault(pos, []).append(l)
1834 elif l.startswith(b' >>> '): # python inlines
1840 elif l.startswith(b' >>> '): # python inlines
1835 after.setdefault(pos, []).append(l)
1841 after.setdefault(pos, []).append(l)
1836 prepos = pos
1842 prepos = pos
1837 pos = n
1843 pos = n
1838 if not inpython:
1844 if not inpython:
1839 # We've just entered a Python block. Add the header.
1845 # We've just entered a Python block. Add the header.
1840 inpython = True
1846 inpython = True
1841 addsalt(prepos, False) # Make sure we report the exit code.
1847 addsalt(prepos, False) # Make sure we report the exit code.
1842 script.append(b'"%s" -m heredoctest <<EOF\n' % PYTHON)
1848 script.append(b'"%s" -m heredoctest <<EOF\n' % PYTHON)
1843 addsalt(n, True)
1849 addsalt(n, True)
1844 script.append(l[2:])
1850 script.append(l[2:])
1845 elif l.startswith(b' ... '): # python inlines
1851 elif l.startswith(b' ... '): # python inlines
1846 after.setdefault(prepos, []).append(l)
1852 after.setdefault(prepos, []).append(l)
1847 script.append(l[2:])
1853 script.append(l[2:])
1848 elif l.startswith(b' $ '): # commands
1854 elif l.startswith(b' $ '): # commands
1849 if inpython:
1855 if inpython:
1850 script.append(b'EOF\n')
1856 script.append(b'EOF\n')
1851 inpython = False
1857 inpython = False
1852 after.setdefault(pos, []).append(l)
1858 after.setdefault(pos, []).append(l)
1853 prepos = pos
1859 prepos = pos
1854 pos = n
1860 pos = n
1855 addsalt(n, False)
1861 addsalt(n, False)
1856 rawcmd = l[4:]
1862 rawcmd = l[4:]
1857 cmd = rawcmd.split()
1863 cmd = rawcmd.split()
1858 toggletrace(rawcmd)
1864 toggletrace(rawcmd)
1859 if len(cmd) == 2 and cmd[0] == b'cd':
1865 if len(cmd) == 2 and cmd[0] == b'cd':
1860 rawcmd = b'cd %s || exit 1\n' % cmd[1]
1866 rawcmd = b'cd %s || exit 1\n' % cmd[1]
1861 script.append(rawcmd)
1867 script.append(rawcmd)
1862 elif l.startswith(b' > '): # continuations
1868 elif l.startswith(b' > '): # continuations
1863 after.setdefault(prepos, []).append(l)
1869 after.setdefault(prepos, []).append(l)
1864 script.append(l[4:])
1870 script.append(l[4:])
1865 elif l.startswith(b' '): # results
1871 elif l.startswith(b' '): # results
1866 # Queue up a list of expected results.
1872 # Queue up a list of expected results.
1867 expected.setdefault(pos, []).append(l[2:])
1873 expected.setdefault(pos, []).append(l[2:])
1868 else:
1874 else:
1869 if inpython:
1875 if inpython:
1870 script.append(b'EOF\n')
1876 script.append(b'EOF\n')
1871 inpython = False
1877 inpython = False
1872 # Non-command/result. Queue up for merged output.
1878 # Non-command/result. Queue up for merged output.
1873 after.setdefault(pos, []).append(l)
1879 after.setdefault(pos, []).append(l)
1874
1880
1875 if inpython:
1881 if inpython:
1876 script.append(b'EOF\n')
1882 script.append(b'EOF\n')
1877 if skipping is not None:
1883 if skipping is not None:
1878 after.setdefault(pos, []).append(b' !!! missing #endif\n')
1884 after.setdefault(pos, []).append(b' !!! missing #endif\n')
1879 addsalt(n + 1, False)
1885 addsalt(n + 1, False)
1880 # Need to end any current per-command trace
1886 # Need to end any current per-command trace
1881 if activetrace:
1887 if activetrace:
1882 toggletrace()
1888 toggletrace()
1883 return salt, script, after, expected
1889 return salt, script, after, expected
1884
1890
1885 def _processoutput(self, exitcode, output, salt, after, expected):
1891 def _processoutput(self, exitcode, output, salt, after, expected):
1886 # Merge the script output back into a unified test.
1892 # Merge the script output back into a unified test.
1887 warnonly = WARN_UNDEFINED # 1: not yet; 2: yes; 3: for sure not
1893 warnonly = WARN_UNDEFINED # 1: not yet; 2: yes; 3: for sure not
1888 if exitcode != 0:
1894 if exitcode != 0:
1889 warnonly = WARN_NO
1895 warnonly = WARN_NO
1890
1896
1891 pos = -1
1897 pos = -1
1892 postout = []
1898 postout = []
1893 for out_rawline in output:
1899 for out_rawline in output:
1894 out_line, cmd_line = out_rawline, None
1900 out_line, cmd_line = out_rawline, None
1895 if salt in out_rawline:
1901 if salt in out_rawline:
1896 out_line, cmd_line = out_rawline.split(salt, 1)
1902 out_line, cmd_line = out_rawline.split(salt, 1)
1897
1903
1898 pos, postout, warnonly = self._process_out_line(
1904 pos, postout, warnonly = self._process_out_line(
1899 out_line, pos, postout, expected, warnonly
1905 out_line, pos, postout, expected, warnonly
1900 )
1906 )
1901 pos, postout = self._process_cmd_line(cmd_line, pos, postout, after)
1907 pos, postout = self._process_cmd_line(cmd_line, pos, postout, after)
1902
1908
1903 if pos in after:
1909 if pos in after:
1904 postout += after.pop(pos)
1910 postout += after.pop(pos)
1905
1911
1906 if warnonly == WARN_YES:
1912 if warnonly == WARN_YES:
1907 exitcode = False # Set exitcode to warned.
1913 exitcode = False # Set exitcode to warned.
1908
1914
1909 return exitcode, postout
1915 return exitcode, postout
1910
1916
1911 def _process_out_line(self, out_line, pos, postout, expected, warnonly):
1917 def _process_out_line(self, out_line, pos, postout, expected, warnonly):
1912 while out_line:
1918 while out_line:
1913 if not out_line.endswith(b'\n'):
1919 if not out_line.endswith(b'\n'):
1914 out_line += b' (no-eol)\n'
1920 out_line += b' (no-eol)\n'
1915
1921
1916 # Find the expected output at the current position.
1922 # Find the expected output at the current position.
1917 els = [None]
1923 els = [None]
1918 if expected.get(pos, None):
1924 if expected.get(pos, None):
1919 els = expected[pos]
1925 els = expected[pos]
1920
1926
1921 optional = []
1927 optional = []
1922 for i, el in enumerate(els):
1928 for i, el in enumerate(els):
1923 r = False
1929 r = False
1924 if el:
1930 if el:
1925 r, exact = self.linematch(el, out_line)
1931 r, exact = self.linematch(el, out_line)
1926 if isinstance(r, str):
1932 if isinstance(r, str):
1927 if r == '-glob':
1933 if r == '-glob':
1928 out_line = ''.join(el.rsplit(' (glob)', 1))
1934 out_line = ''.join(el.rsplit(' (glob)', 1))
1929 r = '' # Warn only this line.
1935 r = '' # Warn only this line.
1930 elif r == "retry":
1936 elif r == "retry":
1931 postout.append(b' ' + el)
1937 postout.append(b' ' + el)
1932 else:
1938 else:
1933 log('\ninfo, unknown linematch result: %r\n' % r)
1939 log('\ninfo, unknown linematch result: %r\n' % r)
1934 r = False
1940 r = False
1935 if r:
1941 if r:
1936 els.pop(i)
1942 els.pop(i)
1937 break
1943 break
1938 if el:
1944 if el:
1939 if isoptional(el):
1945 if isoptional(el):
1940 optional.append(i)
1946 optional.append(i)
1941 else:
1947 else:
1942 m = optline.match(el)
1948 m = optline.match(el)
1943 if m:
1949 if m:
1944 conditions = [c for c in m.group(2).split(b' ')]
1950 conditions = [c for c in m.group(2).split(b' ')]
1945
1951
1946 if not self._iftest(conditions):
1952 if not self._iftest(conditions):
1947 optional.append(i)
1953 optional.append(i)
1948 if exact:
1954 if exact:
1949 # Don't allow line to be matches against a later
1955 # Don't allow line to be matches against a later
1950 # line in the output
1956 # line in the output
1951 els.pop(i)
1957 els.pop(i)
1952 break
1958 break
1953
1959
1954 if r:
1960 if r:
1955 if r == "retry":
1961 if r == "retry":
1956 continue
1962 continue
1957 # clean up any optional leftovers
1963 # clean up any optional leftovers
1958 for i in optional:
1964 for i in optional:
1959 postout.append(b' ' + els[i])
1965 postout.append(b' ' + els[i])
1960 for i in reversed(optional):
1966 for i in reversed(optional):
1961 del els[i]
1967 del els[i]
1962 postout.append(b' ' + el)
1968 postout.append(b' ' + el)
1963 else:
1969 else:
1964 if self.NEEDESCAPE(out_line):
1970 if self.NEEDESCAPE(out_line):
1965 out_line = TTest._stringescape(
1971 out_line = TTest._stringescape(
1966 b'%s (esc)\n' % out_line.rstrip(b'\n')
1972 b'%s (esc)\n' % out_line.rstrip(b'\n')
1967 )
1973 )
1968 postout.append(b' ' + out_line) # Let diff deal with it.
1974 postout.append(b' ' + out_line) # Let diff deal with it.
1969 if r != '': # If line failed.
1975 if r != '': # If line failed.
1970 warnonly = WARN_NO
1976 warnonly = WARN_NO
1971 elif warnonly == WARN_UNDEFINED:
1977 elif warnonly == WARN_UNDEFINED:
1972 warnonly = WARN_YES
1978 warnonly = WARN_YES
1973 break
1979 break
1974 else:
1980 else:
1975 # clean up any optional leftovers
1981 # clean up any optional leftovers
1976 while expected.get(pos, None):
1982 while expected.get(pos, None):
1977 el = expected[pos].pop(0)
1983 el = expected[pos].pop(0)
1978 if el:
1984 if el:
1979 if not isoptional(el):
1985 if not isoptional(el):
1980 m = optline.match(el)
1986 m = optline.match(el)
1981 if m:
1987 if m:
1982 conditions = [c for c in m.group(2).split(b' ')]
1988 conditions = [c for c in m.group(2).split(b' ')]
1983
1989
1984 if self._iftest(conditions):
1990 if self._iftest(conditions):
1985 # Don't append as optional line
1991 # Don't append as optional line
1986 continue
1992 continue
1987 else:
1993 else:
1988 continue
1994 continue
1989 postout.append(b' ' + el)
1995 postout.append(b' ' + el)
1990 return pos, postout, warnonly
1996 return pos, postout, warnonly
1991
1997
1992 def _process_cmd_line(self, cmd_line, pos, postout, after):
1998 def _process_cmd_line(self, cmd_line, pos, postout, after):
1993 """process a "command" part of a line from unified test output"""
1999 """process a "command" part of a line from unified test output"""
1994 if cmd_line:
2000 if cmd_line:
1995 # Add on last return code.
2001 # Add on last return code.
1996 ret = int(cmd_line.split()[1])
2002 ret = int(cmd_line.split()[1])
1997 if ret != 0:
2003 if ret != 0:
1998 postout.append(b' [%d]\n' % ret)
2004 postout.append(b' [%d]\n' % ret)
1999 if pos in after:
2005 if pos in after:
2000 # Merge in non-active test bits.
2006 # Merge in non-active test bits.
2001 postout += after.pop(pos)
2007 postout += after.pop(pos)
2002 pos = int(cmd_line.split()[0])
2008 pos = int(cmd_line.split()[0])
2003 return pos, postout
2009 return pos, postout
2004
2010
2005 @staticmethod
2011 @staticmethod
2006 def rematch(el, l):
2012 def rematch(el, l):
2007 try:
2013 try:
2008 # parse any flags at the beginning of the regex. Only 'i' is
2014 # parse any flags at the beginning of the regex. Only 'i' is
2009 # supported right now, but this should be easy to extend.
2015 # supported right now, but this should be easy to extend.
2010 flags, el = re.match(br'^(\(\?i\))?(.*)', el).groups()[0:2]
2016 flags, el = re.match(br'^(\(\?i\))?(.*)', el).groups()[0:2]
2011 flags = flags or b''
2017 flags = flags or b''
2012 el = flags + b'(?:' + el + b')'
2018 el = flags + b'(?:' + el + b')'
2013 # use \Z to ensure that the regex matches to the end of the string
2019 # use \Z to ensure that the regex matches to the end of the string
2014 if os.name == 'nt':
2020 if os.name == 'nt':
2015 return re.match(el + br'\r?\n\Z', l)
2021 return re.match(el + br'\r?\n\Z', l)
2016 return re.match(el + br'\n\Z', l)
2022 return re.match(el + br'\n\Z', l)
2017 except re.error:
2023 except re.error:
2018 # el is an invalid regex
2024 # el is an invalid regex
2019 return False
2025 return False
2020
2026
2021 @staticmethod
2027 @staticmethod
2022 def globmatch(el, l):
2028 def globmatch(el, l):
2023 # The only supported special characters are * and ? plus / which also
2029 # The only supported special characters are * and ? plus / which also
2024 # matches \ on windows. Escaping of these characters is supported.
2030 # matches \ on windows. Escaping of these characters is supported.
2025 if el + b'\n' == l:
2031 if el + b'\n' == l:
2026 if os.altsep:
2032 if os.altsep:
2027 # matching on "/" is not needed for this line
2033 # matching on "/" is not needed for this line
2028 for pat in checkcodeglobpats:
2034 for pat in checkcodeglobpats:
2029 if pat.match(el):
2035 if pat.match(el):
2030 return True
2036 return True
2031 return b'-glob'
2037 return b'-glob'
2032 return True
2038 return True
2033 el = el.replace(b'$LOCALIP', b'*')
2039 el = el.replace(b'$LOCALIP', b'*')
2034 i, n = 0, len(el)
2040 i, n = 0, len(el)
2035 res = b''
2041 res = b''
2036 while i < n:
2042 while i < n:
2037 c = el[i : i + 1]
2043 c = el[i : i + 1]
2038 i += 1
2044 i += 1
2039 if c == b'\\' and i < n and el[i : i + 1] in b'*?\\/':
2045 if c == b'\\' and i < n and el[i : i + 1] in b'*?\\/':
2040 res += el[i - 1 : i + 1]
2046 res += el[i - 1 : i + 1]
2041 i += 1
2047 i += 1
2042 elif c == b'*':
2048 elif c == b'*':
2043 res += b'.*'
2049 res += b'.*'
2044 elif c == b'?':
2050 elif c == b'?':
2045 res += b'.'
2051 res += b'.'
2046 elif c == b'/' and os.altsep:
2052 elif c == b'/' and os.altsep:
2047 res += b'[/\\\\]'
2053 res += b'[/\\\\]'
2048 else:
2054 else:
2049 res += re.escape(c)
2055 res += re.escape(c)
2050 return TTest.rematch(res, l)
2056 return TTest.rematch(res, l)
2051
2057
2052 def linematch(self, el, l):
2058 def linematch(self, el, l):
2053 if el == l: # perfect match (fast)
2059 if el == l: # perfect match (fast)
2054 return True, True
2060 return True, True
2055 retry = False
2061 retry = False
2056 if isoptional(el):
2062 if isoptional(el):
2057 retry = "retry"
2063 retry = "retry"
2058 el = el[: -len(MARK_OPTIONAL)] + b"\n"
2064 el = el[: -len(MARK_OPTIONAL)] + b"\n"
2059 else:
2065 else:
2060 m = optline.match(el)
2066 m = optline.match(el)
2061 if m:
2067 if m:
2062 conditions = [c for c in m.group(2).split(b' ')]
2068 conditions = [c for c in m.group(2).split(b' ')]
2063
2069
2064 el = m.group(1) + b"\n"
2070 el = m.group(1) + b"\n"
2065 if not self._iftest(conditions):
2071 if not self._iftest(conditions):
2066 # listed feature missing, should not match
2072 # listed feature missing, should not match
2067 return "retry", False
2073 return "retry", False
2068
2074
2069 if el.endswith(b" (esc)\n"):
2075 if el.endswith(b" (esc)\n"):
2070 if PYTHON3:
2076 if PYTHON3:
2071 el = el[:-7].decode('unicode_escape') + '\n'
2077 el = el[:-7].decode('unicode_escape') + '\n'
2072 el = el.encode('latin-1')
2078 el = el.encode('latin-1')
2073 else:
2079 else:
2074 el = el[:-7].decode('string-escape') + '\n'
2080 el = el[:-7].decode('string-escape') + '\n'
2075 if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l:
2081 if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l:
2076 return True, True
2082 return True, True
2077 if el.endswith(b" (re)\n"):
2083 if el.endswith(b" (re)\n"):
2078 return (TTest.rematch(el[:-6], l) or retry), False
2084 return (TTest.rematch(el[:-6], l) or retry), False
2079 if el.endswith(b" (glob)\n"):
2085 if el.endswith(b" (glob)\n"):
2080 # ignore '(glob)' added to l by 'replacements'
2086 # ignore '(glob)' added to l by 'replacements'
2081 if l.endswith(b" (glob)\n"):
2087 if l.endswith(b" (glob)\n"):
2082 l = l[:-8] + b"\n"
2088 l = l[:-8] + b"\n"
2083 return (TTest.globmatch(el[:-8], l) or retry), False
2089 return (TTest.globmatch(el[:-8], l) or retry), False
2084 if os.altsep:
2090 if os.altsep:
2085 _l = l.replace(b'\\', b'/')
2091 _l = l.replace(b'\\', b'/')
2086 if el == _l or os.name == 'nt' and el[:-1] + b'\r\n' == _l:
2092 if el == _l or os.name == 'nt' and el[:-1] + b'\r\n' == _l:
2087 return True, True
2093 return True, True
2088 return retry, True
2094 return retry, True
2089
2095
2090 @staticmethod
2096 @staticmethod
2091 def parsehghaveoutput(lines):
2097 def parsehghaveoutput(lines):
2092 '''Parse hghave log lines.
2098 '''Parse hghave log lines.
2093
2099
2094 Return tuple of lists (missing, failed):
2100 Return tuple of lists (missing, failed):
2095 * the missing/unknown features
2101 * the missing/unknown features
2096 * the features for which existence check failed'''
2102 * the features for which existence check failed'''
2097 missing = []
2103 missing = []
2098 failed = []
2104 failed = []
2099 for line in lines:
2105 for line in lines:
2100 if line.startswith(TTest.SKIPPED_PREFIX):
2106 if line.startswith(TTest.SKIPPED_PREFIX):
2101 line = line.splitlines()[0]
2107 line = line.splitlines()[0]
2102 missing.append(_bytes2sys(line[len(TTest.SKIPPED_PREFIX) :]))
2108 missing.append(_bytes2sys(line[len(TTest.SKIPPED_PREFIX) :]))
2103 elif line.startswith(TTest.FAILED_PREFIX):
2109 elif line.startswith(TTest.FAILED_PREFIX):
2104 line = line.splitlines()[0]
2110 line = line.splitlines()[0]
2105 failed.append(_bytes2sys(line[len(TTest.FAILED_PREFIX) :]))
2111 failed.append(_bytes2sys(line[len(TTest.FAILED_PREFIX) :]))
2106
2112
2107 return missing, failed
2113 return missing, failed
2108
2114
2109 @staticmethod
2115 @staticmethod
2110 def _escapef(m):
2116 def _escapef(m):
2111 return TTest.ESCAPEMAP[m.group(0)]
2117 return TTest.ESCAPEMAP[m.group(0)]
2112
2118
2113 @staticmethod
2119 @staticmethod
2114 def _stringescape(s):
2120 def _stringescape(s):
2115 return TTest.ESCAPESUB(TTest._escapef, s)
2121 return TTest.ESCAPESUB(TTest._escapef, s)
2116
2122
2117
2123
2118 iolock = threading.RLock()
2124 iolock = threading.RLock()
2119 firstlock = threading.RLock()
2125 firstlock = threading.RLock()
2120 firsterror = False
2126 firsterror = False
2121
2127
2122
2128
2123 class TestResult(unittest._TextTestResult):
2129 class TestResult(unittest._TextTestResult):
2124 """Holds results when executing via unittest."""
2130 """Holds results when executing via unittest."""
2125
2131
2126 # Don't worry too much about accessing the non-public _TextTestResult.
2132 # Don't worry too much about accessing the non-public _TextTestResult.
2127 # It is relatively common in Python testing tools.
2133 # It is relatively common in Python testing tools.
2128 def __init__(self, options, *args, **kwargs):
2134 def __init__(self, options, *args, **kwargs):
2129 super(TestResult, self).__init__(*args, **kwargs)
2135 super(TestResult, self).__init__(*args, **kwargs)
2130
2136
2131 self._options = options
2137 self._options = options
2132
2138
2133 # unittest.TestResult didn't have skipped until 2.7. We need to
2139 # unittest.TestResult didn't have skipped until 2.7. We need to
2134 # polyfill it.
2140 # polyfill it.
2135 self.skipped = []
2141 self.skipped = []
2136
2142
2137 # We have a custom "ignored" result that isn't present in any Python
2143 # We have a custom "ignored" result that isn't present in any Python
2138 # unittest implementation. It is very similar to skipped. It may make
2144 # unittest implementation. It is very similar to skipped. It may make
2139 # sense to map it into skip some day.
2145 # sense to map it into skip some day.
2140 self.ignored = []
2146 self.ignored = []
2141
2147
2142 self.times = []
2148 self.times = []
2143 self._firststarttime = None
2149 self._firststarttime = None
2144 # Data stored for the benefit of generating xunit reports.
2150 # Data stored for the benefit of generating xunit reports.
2145 self.successes = []
2151 self.successes = []
2146 self.faildata = {}
2152 self.faildata = {}
2147
2153
2148 if options.color == 'auto':
2154 if options.color == 'auto':
2149 self.color = pygmentspresent and self.stream.isatty()
2155 self.color = pygmentspresent and self.stream.isatty()
2150 elif options.color == 'never':
2156 elif options.color == 'never':
2151 self.color = False
2157 self.color = False
2152 else: # 'always', for testing purposes
2158 else: # 'always', for testing purposes
2153 self.color = pygmentspresent
2159 self.color = pygmentspresent
2154
2160
2155 def onStart(self, test):
2161 def onStart(self, test):
2156 """ Can be overriden by custom TestResult
2162 """ Can be overriden by custom TestResult
2157 """
2163 """
2158
2164
2159 def onEnd(self):
2165 def onEnd(self):
2160 """ Can be overriden by custom TestResult
2166 """ Can be overriden by custom TestResult
2161 """
2167 """
2162
2168
2163 def addFailure(self, test, reason):
2169 def addFailure(self, test, reason):
2164 self.failures.append((test, reason))
2170 self.failures.append((test, reason))
2165
2171
2166 if self._options.first:
2172 if self._options.first:
2167 self.stop()
2173 self.stop()
2168 else:
2174 else:
2169 with iolock:
2175 with iolock:
2170 if reason == "timed out":
2176 if reason == "timed out":
2171 self.stream.write('t')
2177 self.stream.write('t')
2172 else:
2178 else:
2173 if not self._options.nodiff:
2179 if not self._options.nodiff:
2174 self.stream.write('\n')
2180 self.stream.write('\n')
2175 # Exclude the '\n' from highlighting to lex correctly
2181 # Exclude the '\n' from highlighting to lex correctly
2176 formatted = 'ERROR: %s output changed\n' % test
2182 formatted = 'ERROR: %s output changed\n' % test
2177 self.stream.write(highlightmsg(formatted, self.color))
2183 self.stream.write(highlightmsg(formatted, self.color))
2178 self.stream.write('!')
2184 self.stream.write('!')
2179
2185
2180 self.stream.flush()
2186 self.stream.flush()
2181
2187
2182 def addSuccess(self, test):
2188 def addSuccess(self, test):
2183 with iolock:
2189 with iolock:
2184 super(TestResult, self).addSuccess(test)
2190 super(TestResult, self).addSuccess(test)
2185 self.successes.append(test)
2191 self.successes.append(test)
2186
2192
2187 def addError(self, test, err):
2193 def addError(self, test, err):
2188 super(TestResult, self).addError(test, err)
2194 super(TestResult, self).addError(test, err)
2189 if self._options.first:
2195 if self._options.first:
2190 self.stop()
2196 self.stop()
2191
2197
2192 # Polyfill.
2198 # Polyfill.
2193 def addSkip(self, test, reason):
2199 def addSkip(self, test, reason):
2194 self.skipped.append((test, reason))
2200 self.skipped.append((test, reason))
2195 with iolock:
2201 with iolock:
2196 if self.showAll:
2202 if self.showAll:
2197 self.stream.writeln('skipped %s' % reason)
2203 self.stream.writeln('skipped %s' % reason)
2198 else:
2204 else:
2199 self.stream.write('s')
2205 self.stream.write('s')
2200 self.stream.flush()
2206 self.stream.flush()
2201
2207
2202 def addIgnore(self, test, reason):
2208 def addIgnore(self, test, reason):
2203 self.ignored.append((test, reason))
2209 self.ignored.append((test, reason))
2204 with iolock:
2210 with iolock:
2205 if self.showAll:
2211 if self.showAll:
2206 self.stream.writeln('ignored %s' % reason)
2212 self.stream.writeln('ignored %s' % reason)
2207 else:
2213 else:
2208 if reason not in ('not retesting', "doesn't match keyword"):
2214 if reason not in ('not retesting', "doesn't match keyword"):
2209 self.stream.write('i')
2215 self.stream.write('i')
2210 else:
2216 else:
2211 self.testsRun += 1
2217 self.testsRun += 1
2212 self.stream.flush()
2218 self.stream.flush()
2213
2219
2214 def addOutputMismatch(self, test, ret, got, expected):
2220 def addOutputMismatch(self, test, ret, got, expected):
2215 """Record a mismatch in test output for a particular test."""
2221 """Record a mismatch in test output for a particular test."""
2216 if self.shouldStop or firsterror:
2222 if self.shouldStop or firsterror:
2217 # don't print, some other test case already failed and
2223 # don't print, some other test case already failed and
2218 # printed, we're just stale and probably failed due to our
2224 # printed, we're just stale and probably failed due to our
2219 # temp dir getting cleaned up.
2225 # temp dir getting cleaned up.
2220 return
2226 return
2221
2227
2222 accepted = False
2228 accepted = False
2223 lines = []
2229 lines = []
2224
2230
2225 with iolock:
2231 with iolock:
2226 if self._options.nodiff:
2232 if self._options.nodiff:
2227 pass
2233 pass
2228 elif self._options.view:
2234 elif self._options.view:
2229 v = self._options.view
2235 v = self._options.view
2230 subprocess.call(
2236 subprocess.call(
2231 r'"%s" "%s" "%s"'
2237 r'"%s" "%s" "%s"'
2232 % (v, _bytes2sys(test.refpath), _bytes2sys(test.errpath)),
2238 % (v, _bytes2sys(test.refpath), _bytes2sys(test.errpath)),
2233 shell=True,
2239 shell=True,
2234 )
2240 )
2235 else:
2241 else:
2236 servefail, lines = getdiff(
2242 servefail, lines = getdiff(
2237 expected, got, test.refpath, test.errpath
2243 expected, got, test.refpath, test.errpath
2238 )
2244 )
2239 self.stream.write('\n')
2245 self.stream.write('\n')
2240 for line in lines:
2246 for line in lines:
2241 line = highlightdiff(line, self.color)
2247 line = highlightdiff(line, self.color)
2242 if PYTHON3:
2248 if PYTHON3:
2243 self.stream.flush()
2249 self.stream.flush()
2244 self.stream.buffer.write(line)
2250 self.stream.buffer.write(line)
2245 self.stream.buffer.flush()
2251 self.stream.buffer.flush()
2246 else:
2252 else:
2247 self.stream.write(line)
2253 self.stream.write(line)
2248 self.stream.flush()
2254 self.stream.flush()
2249
2255
2250 if servefail:
2256 if servefail:
2251 raise test.failureException(
2257 raise test.failureException(
2252 'server failed to start (HGPORT=%s)' % test._startport
2258 'server failed to start (HGPORT=%s)' % test._startport
2253 )
2259 )
2254
2260
2255 # handle interactive prompt without releasing iolock
2261 # handle interactive prompt without releasing iolock
2256 if self._options.interactive:
2262 if self._options.interactive:
2257 if test.readrefout() != expected:
2263 if test.readrefout() != expected:
2258 self.stream.write(
2264 self.stream.write(
2259 'Reference output has changed (run again to prompt '
2265 'Reference output has changed (run again to prompt '
2260 'changes)'
2266 'changes)'
2261 )
2267 )
2262 else:
2268 else:
2263 self.stream.write('Accept this change? [y/N] ')
2269 self.stream.write('Accept this change? [y/N] ')
2264 self.stream.flush()
2270 self.stream.flush()
2265 answer = sys.stdin.readline().strip()
2271 answer = sys.stdin.readline().strip()
2266 if answer.lower() in ('y', 'yes'):
2272 if answer.lower() in ('y', 'yes'):
2267 if test.path.endswith(b'.t'):
2273 if test.path.endswith(b'.t'):
2268 rename(test.errpath, test.path)
2274 rename(test.errpath, test.path)
2269 else:
2275 else:
2270 rename(test.errpath, '%s.out' % test.path)
2276 rename(test.errpath, '%s.out' % test.path)
2271 accepted = True
2277 accepted = True
2272 if not accepted:
2278 if not accepted:
2273 self.faildata[test.name] = b''.join(lines)
2279 self.faildata[test.name] = b''.join(lines)
2274
2280
2275 return accepted
2281 return accepted
2276
2282
2277 def startTest(self, test):
2283 def startTest(self, test):
2278 super(TestResult, self).startTest(test)
2284 super(TestResult, self).startTest(test)
2279
2285
2280 # os.times module computes the user time and system time spent by
2286 # os.times module computes the user time and system time spent by
2281 # child's processes along with real elapsed time taken by a process.
2287 # child's processes along with real elapsed time taken by a process.
2282 # This module has one limitation. It can only work for Linux user
2288 # This module has one limitation. It can only work for Linux user
2283 # and not for Windows. Hence why we fall back to another function
2289 # and not for Windows. Hence why we fall back to another function
2284 # for wall time calculations.
2290 # for wall time calculations.
2285 test.started_times = os.times()
2291 test.started_times = os.times()
2286 # TODO use a monotonic clock once support for Python 2.7 is dropped.
2292 # TODO use a monotonic clock once support for Python 2.7 is dropped.
2287 test.started_time = time.time()
2293 test.started_time = time.time()
2288 if self._firststarttime is None: # thread racy but irrelevant
2294 if self._firststarttime is None: # thread racy but irrelevant
2289 self._firststarttime = test.started_time
2295 self._firststarttime = test.started_time
2290
2296
2291 def stopTest(self, test, interrupted=False):
2297 def stopTest(self, test, interrupted=False):
2292 super(TestResult, self).stopTest(test)
2298 super(TestResult, self).stopTest(test)
2293
2299
2294 test.stopped_times = os.times()
2300 test.stopped_times = os.times()
2295 stopped_time = time.time()
2301 stopped_time = time.time()
2296
2302
2297 starttime = test.started_times
2303 starttime = test.started_times
2298 endtime = test.stopped_times
2304 endtime = test.stopped_times
2299 origin = self._firststarttime
2305 origin = self._firststarttime
2300 self.times.append(
2306 self.times.append(
2301 (
2307 (
2302 test.name,
2308 test.name,
2303 endtime[2] - starttime[2], # user space CPU time
2309 endtime[2] - starttime[2], # user space CPU time
2304 endtime[3] - starttime[3], # sys space CPU time
2310 endtime[3] - starttime[3], # sys space CPU time
2305 stopped_time - test.started_time, # real time
2311 stopped_time - test.started_time, # real time
2306 test.started_time - origin, # start date in run context
2312 test.started_time - origin, # start date in run context
2307 stopped_time - origin, # end date in run context
2313 stopped_time - origin, # end date in run context
2308 )
2314 )
2309 )
2315 )
2310
2316
2311 if interrupted:
2317 if interrupted:
2312 with iolock:
2318 with iolock:
2313 self.stream.writeln(
2319 self.stream.writeln(
2314 'INTERRUPTED: %s (after %d seconds)'
2320 'INTERRUPTED: %s (after %d seconds)'
2315 % (test.name, self.times[-1][3])
2321 % (test.name, self.times[-1][3])
2316 )
2322 )
2317
2323
2318
2324
2319 def getTestResult():
2325 def getTestResult():
2320 """
2326 """
2321 Returns the relevant test result
2327 Returns the relevant test result
2322 """
2328 """
2323 if "CUSTOM_TEST_RESULT" in os.environ:
2329 if "CUSTOM_TEST_RESULT" in os.environ:
2324 testresultmodule = __import__(os.environ["CUSTOM_TEST_RESULT"])
2330 testresultmodule = __import__(os.environ["CUSTOM_TEST_RESULT"])
2325 return testresultmodule.TestResult
2331 return testresultmodule.TestResult
2326 else:
2332 else:
2327 return TestResult
2333 return TestResult
2328
2334
2329
2335
2330 class TestSuite(unittest.TestSuite):
2336 class TestSuite(unittest.TestSuite):
2331 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
2337 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
2332
2338
2333 def __init__(
2339 def __init__(
2334 self,
2340 self,
2335 testdir,
2341 testdir,
2336 jobs=1,
2342 jobs=1,
2337 whitelist=None,
2343 whitelist=None,
2338 blacklist=None,
2344 blacklist=None,
2339 retest=False,
2345 retest=False,
2340 keywords=None,
2346 keywords=None,
2341 loop=False,
2347 loop=False,
2342 runs_per_test=1,
2348 runs_per_test=1,
2343 loadtest=None,
2349 loadtest=None,
2344 showchannels=False,
2350 showchannels=False,
2345 *args,
2351 *args,
2346 **kwargs
2352 **kwargs
2347 ):
2353 ):
2348 """Create a new instance that can run tests with a configuration.
2354 """Create a new instance that can run tests with a configuration.
2349
2355
2350 testdir specifies the directory where tests are executed from. This
2356 testdir specifies the directory where tests are executed from. This
2351 is typically the ``tests`` directory from Mercurial's source
2357 is typically the ``tests`` directory from Mercurial's source
2352 repository.
2358 repository.
2353
2359
2354 jobs specifies the number of jobs to run concurrently. Each test
2360 jobs specifies the number of jobs to run concurrently. Each test
2355 executes on its own thread. Tests actually spawn new processes, so
2361 executes on its own thread. Tests actually spawn new processes, so
2356 state mutation should not be an issue.
2362 state mutation should not be an issue.
2357
2363
2358 If there is only one job, it will use the main thread.
2364 If there is only one job, it will use the main thread.
2359
2365
2360 whitelist and blacklist denote tests that have been whitelisted and
2366 whitelist and blacklist denote tests that have been whitelisted and
2361 blacklisted, respectively. These arguments don't belong in TestSuite.
2367 blacklisted, respectively. These arguments don't belong in TestSuite.
2362 Instead, whitelist and blacklist should be handled by the thing that
2368 Instead, whitelist and blacklist should be handled by the thing that
2363 populates the TestSuite with tests. They are present to preserve
2369 populates the TestSuite with tests. They are present to preserve
2364 backwards compatible behavior which reports skipped tests as part
2370 backwards compatible behavior which reports skipped tests as part
2365 of the results.
2371 of the results.
2366
2372
2367 retest denotes whether to retest failed tests. This arguably belongs
2373 retest denotes whether to retest failed tests. This arguably belongs
2368 outside of TestSuite.
2374 outside of TestSuite.
2369
2375
2370 keywords denotes key words that will be used to filter which tests
2376 keywords denotes key words that will be used to filter which tests
2371 to execute. This arguably belongs outside of TestSuite.
2377 to execute. This arguably belongs outside of TestSuite.
2372
2378
2373 loop denotes whether to loop over tests forever.
2379 loop denotes whether to loop over tests forever.
2374 """
2380 """
2375 super(TestSuite, self).__init__(*args, **kwargs)
2381 super(TestSuite, self).__init__(*args, **kwargs)
2376
2382
2377 self._jobs = jobs
2383 self._jobs = jobs
2378 self._whitelist = whitelist
2384 self._whitelist = whitelist
2379 self._blacklist = blacklist
2385 self._blacklist = blacklist
2380 self._retest = retest
2386 self._retest = retest
2381 self._keywords = keywords
2387 self._keywords = keywords
2382 self._loop = loop
2388 self._loop = loop
2383 self._runs_per_test = runs_per_test
2389 self._runs_per_test = runs_per_test
2384 self._loadtest = loadtest
2390 self._loadtest = loadtest
2385 self._showchannels = showchannels
2391 self._showchannels = showchannels
2386
2392
2387 def run(self, result):
2393 def run(self, result):
2388 # We have a number of filters that need to be applied. We do this
2394 # We have a number of filters that need to be applied. We do this
2389 # here instead of inside Test because it makes the running logic for
2395 # here instead of inside Test because it makes the running logic for
2390 # Test simpler.
2396 # Test simpler.
2391 tests = []
2397 tests = []
2392 num_tests = [0]
2398 num_tests = [0]
2393 for test in self._tests:
2399 for test in self._tests:
2394
2400
2395 def get():
2401 def get():
2396 num_tests[0] += 1
2402 num_tests[0] += 1
2397 if getattr(test, 'should_reload', False):
2403 if getattr(test, 'should_reload', False):
2398 return self._loadtest(test, num_tests[0])
2404 return self._loadtest(test, num_tests[0])
2399 return test
2405 return test
2400
2406
2401 if not os.path.exists(test.path):
2407 if not os.path.exists(test.path):
2402 result.addSkip(test, "Doesn't exist")
2408 result.addSkip(test, "Doesn't exist")
2403 continue
2409 continue
2404
2410
2405 if not (self._whitelist and test.bname in self._whitelist):
2411 if not (self._whitelist and test.bname in self._whitelist):
2406 if self._blacklist and test.bname in self._blacklist:
2412 if self._blacklist and test.bname in self._blacklist:
2407 result.addSkip(test, 'blacklisted')
2413 result.addSkip(test, 'blacklisted')
2408 continue
2414 continue
2409
2415
2410 if self._retest and not os.path.exists(test.errpath):
2416 if self._retest and not os.path.exists(test.errpath):
2411 result.addIgnore(test, 'not retesting')
2417 result.addIgnore(test, 'not retesting')
2412 continue
2418 continue
2413
2419
2414 if self._keywords:
2420 if self._keywords:
2415 with open(test.path, 'rb') as f:
2421 with open(test.path, 'rb') as f:
2416 t = f.read().lower() + test.bname.lower()
2422 t = f.read().lower() + test.bname.lower()
2417 ignored = False
2423 ignored = False
2418 for k in self._keywords.lower().split():
2424 for k in self._keywords.lower().split():
2419 if k not in t:
2425 if k not in t:
2420 result.addIgnore(test, "doesn't match keyword")
2426 result.addIgnore(test, "doesn't match keyword")
2421 ignored = True
2427 ignored = True
2422 break
2428 break
2423
2429
2424 if ignored:
2430 if ignored:
2425 continue
2431 continue
2426 for _ in xrange(self._runs_per_test):
2432 for _ in xrange(self._runs_per_test):
2427 tests.append(get())
2433 tests.append(get())
2428
2434
2429 runtests = list(tests)
2435 runtests = list(tests)
2430 done = queue.Queue()
2436 done = queue.Queue()
2431 running = 0
2437 running = 0
2432
2438
2433 channels = [""] * self._jobs
2439 channels = [""] * self._jobs
2434
2440
2435 def job(test, result):
2441 def job(test, result):
2436 for n, v in enumerate(channels):
2442 for n, v in enumerate(channels):
2437 if not v:
2443 if not v:
2438 channel = n
2444 channel = n
2439 break
2445 break
2440 else:
2446 else:
2441 raise ValueError('Could not find output channel')
2447 raise ValueError('Could not find output channel')
2442 channels[channel] = "=" + test.name[5:].split(".")[0]
2448 channels[channel] = "=" + test.name[5:].split(".")[0]
2443 try:
2449 try:
2444 test(result)
2450 test(result)
2445 done.put(None)
2451 done.put(None)
2446 except KeyboardInterrupt:
2452 except KeyboardInterrupt:
2447 pass
2453 pass
2448 except: # re-raises
2454 except: # re-raises
2449 done.put(('!', test, 'run-test raised an error, see traceback'))
2455 done.put(('!', test, 'run-test raised an error, see traceback'))
2450 raise
2456 raise
2451 finally:
2457 finally:
2452 try:
2458 try:
2453 channels[channel] = ''
2459 channels[channel] = ''
2454 except IndexError:
2460 except IndexError:
2455 pass
2461 pass
2456
2462
2457 def stat():
2463 def stat():
2458 count = 0
2464 count = 0
2459 while channels:
2465 while channels:
2460 d = '\n%03s ' % count
2466 d = '\n%03s ' % count
2461 for n, v in enumerate(channels):
2467 for n, v in enumerate(channels):
2462 if v:
2468 if v:
2463 d += v[0]
2469 d += v[0]
2464 channels[n] = v[1:] or '.'
2470 channels[n] = v[1:] or '.'
2465 else:
2471 else:
2466 d += ' '
2472 d += ' '
2467 d += ' '
2473 d += ' '
2468 with iolock:
2474 with iolock:
2469 sys.stdout.write(d + ' ')
2475 sys.stdout.write(d + ' ')
2470 sys.stdout.flush()
2476 sys.stdout.flush()
2471 for x in xrange(10):
2477 for x in xrange(10):
2472 if channels:
2478 if channels:
2473 time.sleep(0.1)
2479 time.sleep(0.1)
2474 count += 1
2480 count += 1
2475
2481
2476 stoppedearly = False
2482 stoppedearly = False
2477
2483
2478 if self._showchannels:
2484 if self._showchannels:
2479 statthread = threading.Thread(target=stat, name="stat")
2485 statthread = threading.Thread(target=stat, name="stat")
2480 statthread.start()
2486 statthread.start()
2481
2487
2482 try:
2488 try:
2483 while tests or running:
2489 while tests or running:
2484 if not done.empty() or running == self._jobs or not tests:
2490 if not done.empty() or running == self._jobs or not tests:
2485 try:
2491 try:
2486 done.get(True, 1)
2492 done.get(True, 1)
2487 running -= 1
2493 running -= 1
2488 if result and result.shouldStop:
2494 if result and result.shouldStop:
2489 stoppedearly = True
2495 stoppedearly = True
2490 break
2496 break
2491 except queue.Empty:
2497 except queue.Empty:
2492 continue
2498 continue
2493 if tests and not running == self._jobs:
2499 if tests and not running == self._jobs:
2494 test = tests.pop(0)
2500 test = tests.pop(0)
2495 if self._loop:
2501 if self._loop:
2496 if getattr(test, 'should_reload', False):
2502 if getattr(test, 'should_reload', False):
2497 num_tests[0] += 1
2503 num_tests[0] += 1
2498 tests.append(self._loadtest(test, num_tests[0]))
2504 tests.append(self._loadtest(test, num_tests[0]))
2499 else:
2505 else:
2500 tests.append(test)
2506 tests.append(test)
2501 if self._jobs == 1:
2507 if self._jobs == 1:
2502 job(test, result)
2508 job(test, result)
2503 else:
2509 else:
2504 t = threading.Thread(
2510 t = threading.Thread(
2505 target=job, name=test.name, args=(test, result)
2511 target=job, name=test.name, args=(test, result)
2506 )
2512 )
2507 t.start()
2513 t.start()
2508 running += 1
2514 running += 1
2509
2515
2510 # If we stop early we still need to wait on started tests to
2516 # If we stop early we still need to wait on started tests to
2511 # finish. Otherwise, there is a race between the test completing
2517 # finish. Otherwise, there is a race between the test completing
2512 # and the test's cleanup code running. This could result in the
2518 # and the test's cleanup code running. This could result in the
2513 # test reporting incorrect.
2519 # test reporting incorrect.
2514 if stoppedearly:
2520 if stoppedearly:
2515 while running:
2521 while running:
2516 try:
2522 try:
2517 done.get(True, 1)
2523 done.get(True, 1)
2518 running -= 1
2524 running -= 1
2519 except queue.Empty:
2525 except queue.Empty:
2520 continue
2526 continue
2521 except KeyboardInterrupt:
2527 except KeyboardInterrupt:
2522 for test in runtests:
2528 for test in runtests:
2523 test.abort()
2529 test.abort()
2524
2530
2525 channels = []
2531 channels = []
2526
2532
2527 return result
2533 return result
2528
2534
2529
2535
2530 # Save the most recent 5 wall-clock runtimes of each test to a
2536 # Save the most recent 5 wall-clock runtimes of each test to a
2531 # human-readable text file named .testtimes. Tests are sorted
2537 # human-readable text file named .testtimes. Tests are sorted
2532 # alphabetically, while times for each test are listed from oldest to
2538 # alphabetically, while times for each test are listed from oldest to
2533 # newest.
2539 # newest.
2534
2540
2535
2541
2536 def loadtimes(outputdir):
2542 def loadtimes(outputdir):
2537 times = []
2543 times = []
2538 try:
2544 try:
2539 with open(os.path.join(outputdir, b'.testtimes')) as fp:
2545 with open(os.path.join(outputdir, b'.testtimes')) as fp:
2540 for line in fp:
2546 for line in fp:
2541 m = re.match('(.*?) ([0-9. ]+)', line)
2547 m = re.match('(.*?) ([0-9. ]+)', line)
2542 times.append(
2548 times.append(
2543 (m.group(1), [float(t) for t in m.group(2).split()])
2549 (m.group(1), [float(t) for t in m.group(2).split()])
2544 )
2550 )
2545 except IOError as err:
2551 except IOError as err:
2546 if err.errno != errno.ENOENT:
2552 if err.errno != errno.ENOENT:
2547 raise
2553 raise
2548 return times
2554 return times
2549
2555
2550
2556
2551 def savetimes(outputdir, result):
2557 def savetimes(outputdir, result):
2552 saved = dict(loadtimes(outputdir))
2558 saved = dict(loadtimes(outputdir))
2553 maxruns = 5
2559 maxruns = 5
2554 skipped = {str(t[0]) for t in result.skipped}
2560 skipped = {str(t[0]) for t in result.skipped}
2555 for tdata in result.times:
2561 for tdata in result.times:
2556 test, real = tdata[0], tdata[3]
2562 test, real = tdata[0], tdata[3]
2557 if test not in skipped:
2563 if test not in skipped:
2558 ts = saved.setdefault(test, [])
2564 ts = saved.setdefault(test, [])
2559 ts.append(real)
2565 ts.append(real)
2560 ts[:] = ts[-maxruns:]
2566 ts[:] = ts[-maxruns:]
2561
2567
2562 fd, tmpname = tempfile.mkstemp(
2568 fd, tmpname = tempfile.mkstemp(
2563 prefix=b'.testtimes', dir=outputdir, text=True
2569 prefix=b'.testtimes', dir=outputdir, text=True
2564 )
2570 )
2565 with os.fdopen(fd, 'w') as fp:
2571 with os.fdopen(fd, 'w') as fp:
2566 for name, ts in sorted(saved.items()):
2572 for name, ts in sorted(saved.items()):
2567 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
2573 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
2568 timepath = os.path.join(outputdir, b'.testtimes')
2574 timepath = os.path.join(outputdir, b'.testtimes')
2569 try:
2575 try:
2570 os.unlink(timepath)
2576 os.unlink(timepath)
2571 except OSError:
2577 except OSError:
2572 pass
2578 pass
2573 try:
2579 try:
2574 os.rename(tmpname, timepath)
2580 os.rename(tmpname, timepath)
2575 except OSError:
2581 except OSError:
2576 pass
2582 pass
2577
2583
2578
2584
2579 class TextTestRunner(unittest.TextTestRunner):
2585 class TextTestRunner(unittest.TextTestRunner):
2580 """Custom unittest test runner that uses appropriate settings."""
2586 """Custom unittest test runner that uses appropriate settings."""
2581
2587
2582 def __init__(self, runner, *args, **kwargs):
2588 def __init__(self, runner, *args, **kwargs):
2583 super(TextTestRunner, self).__init__(*args, **kwargs)
2589 super(TextTestRunner, self).__init__(*args, **kwargs)
2584
2590
2585 self._runner = runner
2591 self._runner = runner
2586
2592
2587 self._result = getTestResult()(
2593 self._result = getTestResult()(
2588 self._runner.options, self.stream, self.descriptions, self.verbosity
2594 self._runner.options, self.stream, self.descriptions, self.verbosity
2589 )
2595 )
2590
2596
2591 def listtests(self, test):
2597 def listtests(self, test):
2592 test = sorted(test, key=lambda t: t.name)
2598 test = sorted(test, key=lambda t: t.name)
2593
2599
2594 self._result.onStart(test)
2600 self._result.onStart(test)
2595
2601
2596 for t in test:
2602 for t in test:
2597 print(t.name)
2603 print(t.name)
2598 self._result.addSuccess(t)
2604 self._result.addSuccess(t)
2599
2605
2600 if self._runner.options.xunit:
2606 if self._runner.options.xunit:
2601 with open(self._runner.options.xunit, "wb") as xuf:
2607 with open(self._runner.options.xunit, "wb") as xuf:
2602 self._writexunit(self._result, xuf)
2608 self._writexunit(self._result, xuf)
2603
2609
2604 if self._runner.options.json:
2610 if self._runner.options.json:
2605 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2611 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2606 with open(jsonpath, 'w') as fp:
2612 with open(jsonpath, 'w') as fp:
2607 self._writejson(self._result, fp)
2613 self._writejson(self._result, fp)
2608
2614
2609 return self._result
2615 return self._result
2610
2616
2611 def run(self, test):
2617 def run(self, test):
2612 self._result.onStart(test)
2618 self._result.onStart(test)
2613 test(self._result)
2619 test(self._result)
2614
2620
2615 failed = len(self._result.failures)
2621 failed = len(self._result.failures)
2616 skipped = len(self._result.skipped)
2622 skipped = len(self._result.skipped)
2617 ignored = len(self._result.ignored)
2623 ignored = len(self._result.ignored)
2618
2624
2619 with iolock:
2625 with iolock:
2620 self.stream.writeln('')
2626 self.stream.writeln('')
2621
2627
2622 if not self._runner.options.noskips:
2628 if not self._runner.options.noskips:
2623 for test, msg in sorted(
2629 for test, msg in sorted(
2624 self._result.skipped, key=lambda s: s[0].name
2630 self._result.skipped, key=lambda s: s[0].name
2625 ):
2631 ):
2626 formatted = 'Skipped %s: %s\n' % (test.name, msg)
2632 formatted = 'Skipped %s: %s\n' % (test.name, msg)
2627 msg = highlightmsg(formatted, self._result.color)
2633 msg = highlightmsg(formatted, self._result.color)
2628 self.stream.write(msg)
2634 self.stream.write(msg)
2629 for test, msg in sorted(
2635 for test, msg in sorted(
2630 self._result.failures, key=lambda f: f[0].name
2636 self._result.failures, key=lambda f: f[0].name
2631 ):
2637 ):
2632 formatted = 'Failed %s: %s\n' % (test.name, msg)
2638 formatted = 'Failed %s: %s\n' % (test.name, msg)
2633 self.stream.write(highlightmsg(formatted, self._result.color))
2639 self.stream.write(highlightmsg(formatted, self._result.color))
2634 for test, msg in sorted(
2640 for test, msg in sorted(
2635 self._result.errors, key=lambda e: e[0].name
2641 self._result.errors, key=lambda e: e[0].name
2636 ):
2642 ):
2637 self.stream.writeln('Errored %s: %s' % (test.name, msg))
2643 self.stream.writeln('Errored %s: %s' % (test.name, msg))
2638
2644
2639 if self._runner.options.xunit:
2645 if self._runner.options.xunit:
2640 with open(self._runner.options.xunit, "wb") as xuf:
2646 with open(self._runner.options.xunit, "wb") as xuf:
2641 self._writexunit(self._result, xuf)
2647 self._writexunit(self._result, xuf)
2642
2648
2643 if self._runner.options.json:
2649 if self._runner.options.json:
2644 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2650 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2645 with open(jsonpath, 'w') as fp:
2651 with open(jsonpath, 'w') as fp:
2646 self._writejson(self._result, fp)
2652 self._writejson(self._result, fp)
2647
2653
2648 self._runner._checkhglib('Tested')
2654 self._runner._checkhglib('Tested')
2649
2655
2650 savetimes(self._runner._outputdir, self._result)
2656 savetimes(self._runner._outputdir, self._result)
2651
2657
2652 if failed and self._runner.options.known_good_rev:
2658 if failed and self._runner.options.known_good_rev:
2653 self._bisecttests(t for t, m in self._result.failures)
2659 self._bisecttests(t for t, m in self._result.failures)
2654 self.stream.writeln(
2660 self.stream.writeln(
2655 '# Ran %d tests, %d skipped, %d failed.'
2661 '# Ran %d tests, %d skipped, %d failed.'
2656 % (self._result.testsRun, skipped + ignored, failed)
2662 % (self._result.testsRun, skipped + ignored, failed)
2657 )
2663 )
2658 if failed:
2664 if failed:
2659 self.stream.writeln(
2665 self.stream.writeln(
2660 'python hash seed: %s' % os.environ['PYTHONHASHSEED']
2666 'python hash seed: %s' % os.environ['PYTHONHASHSEED']
2661 )
2667 )
2662 if self._runner.options.time:
2668 if self._runner.options.time:
2663 self.printtimes(self._result.times)
2669 self.printtimes(self._result.times)
2664
2670
2665 if self._runner.options.exceptions:
2671 if self._runner.options.exceptions:
2666 exceptions = aggregateexceptions(
2672 exceptions = aggregateexceptions(
2667 os.path.join(self._runner._outputdir, b'exceptions')
2673 os.path.join(self._runner._outputdir, b'exceptions')
2668 )
2674 )
2669
2675
2670 self.stream.writeln('Exceptions Report:')
2676 self.stream.writeln('Exceptions Report:')
2671 self.stream.writeln(
2677 self.stream.writeln(
2672 '%d total from %d frames'
2678 '%d total from %d frames'
2673 % (exceptions['total'], len(exceptions['exceptioncounts']))
2679 % (exceptions['total'], len(exceptions['exceptioncounts']))
2674 )
2680 )
2675 combined = exceptions['combined']
2681 combined = exceptions['combined']
2676 for key in sorted(combined, key=combined.get, reverse=True):
2682 for key in sorted(combined, key=combined.get, reverse=True):
2677 frame, line, exc = key
2683 frame, line, exc = key
2678 totalcount, testcount, leastcount, leasttest = combined[key]
2684 totalcount, testcount, leastcount, leasttest = combined[key]
2679
2685
2680 self.stream.writeln(
2686 self.stream.writeln(
2681 '%d (%d tests)\t%s: %s (%s - %d total)'
2687 '%d (%d tests)\t%s: %s (%s - %d total)'
2682 % (
2688 % (
2683 totalcount,
2689 totalcount,
2684 testcount,
2690 testcount,
2685 frame,
2691 frame,
2686 exc,
2692 exc,
2687 leasttest,
2693 leasttest,
2688 leastcount,
2694 leastcount,
2689 )
2695 )
2690 )
2696 )
2691
2697
2692 self.stream.flush()
2698 self.stream.flush()
2693
2699
2694 return self._result
2700 return self._result
2695
2701
2696 def _bisecttests(self, tests):
2702 def _bisecttests(self, tests):
2697 bisectcmd = ['hg', 'bisect']
2703 bisectcmd = ['hg', 'bisect']
2698 bisectrepo = self._runner.options.bisect_repo
2704 bisectrepo = self._runner.options.bisect_repo
2699 if bisectrepo:
2705 if bisectrepo:
2700 bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
2706 bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
2701
2707
2702 def pread(args):
2708 def pread(args):
2703 env = os.environ.copy()
2709 env = os.environ.copy()
2704 env['HGPLAIN'] = '1'
2710 env['HGPLAIN'] = '1'
2705 p = subprocess.Popen(
2711 p = subprocess.Popen(
2706 args, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=env
2712 args, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=env
2707 )
2713 )
2708 data = p.stdout.read()
2714 data = p.stdout.read()
2709 p.wait()
2715 p.wait()
2710 return data
2716 return data
2711
2717
2712 for test in tests:
2718 for test in tests:
2713 pread(bisectcmd + ['--reset']),
2719 pread(bisectcmd + ['--reset']),
2714 pread(bisectcmd + ['--bad', '.'])
2720 pread(bisectcmd + ['--bad', '.'])
2715 pread(bisectcmd + ['--good', self._runner.options.known_good_rev])
2721 pread(bisectcmd + ['--good', self._runner.options.known_good_rev])
2716 # TODO: we probably need to forward more options
2722 # TODO: we probably need to forward more options
2717 # that alter hg's behavior inside the tests.
2723 # that alter hg's behavior inside the tests.
2718 opts = ''
2724 opts = ''
2719 withhg = self._runner.options.with_hg
2725 withhg = self._runner.options.with_hg
2720 if withhg:
2726 if withhg:
2721 opts += ' --with-hg=%s ' % shellquote(_bytes2sys(withhg))
2727 opts += ' --with-hg=%s ' % shellquote(_bytes2sys(withhg))
2722 rtc = '%s %s %s %s' % (sysexecutable, sys.argv[0], opts, test)
2728 rtc = '%s %s %s %s' % (sysexecutable, sys.argv[0], opts, test)
2723 data = pread(bisectcmd + ['--command', rtc])
2729 data = pread(bisectcmd + ['--command', rtc])
2724 m = re.search(
2730 m = re.search(
2725 (
2731 (
2726 br'\nThe first (?P<goodbad>bad|good) revision '
2732 br'\nThe first (?P<goodbad>bad|good) revision '
2727 br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
2733 br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
2728 br'summary: +(?P<summary>[^\n]+)\n'
2734 br'summary: +(?P<summary>[^\n]+)\n'
2729 ),
2735 ),
2730 data,
2736 data,
2731 (re.MULTILINE | re.DOTALL),
2737 (re.MULTILINE | re.DOTALL),
2732 )
2738 )
2733 if m is None:
2739 if m is None:
2734 self.stream.writeln(
2740 self.stream.writeln(
2735 'Failed to identify failure point for %s' % test
2741 'Failed to identify failure point for %s' % test
2736 )
2742 )
2737 continue
2743 continue
2738 dat = m.groupdict()
2744 dat = m.groupdict()
2739 verb = 'broken' if dat['goodbad'] == b'bad' else 'fixed'
2745 verb = 'broken' if dat['goodbad'] == b'bad' else 'fixed'
2740 self.stream.writeln(
2746 self.stream.writeln(
2741 '%s %s by %s (%s)'
2747 '%s %s by %s (%s)'
2742 % (
2748 % (
2743 test,
2749 test,
2744 verb,
2750 verb,
2745 dat['node'].decode('ascii'),
2751 dat['node'].decode('ascii'),
2746 dat['summary'].decode('utf8', 'ignore'),
2752 dat['summary'].decode('utf8', 'ignore'),
2747 )
2753 )
2748 )
2754 )
2749
2755
2750 def printtimes(self, times):
2756 def printtimes(self, times):
2751 # iolock held by run
2757 # iolock held by run
2752 self.stream.writeln('# Producing time report')
2758 self.stream.writeln('# Producing time report')
2753 times.sort(key=lambda t: (t[3]))
2759 times.sort(key=lambda t: (t[3]))
2754 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
2760 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
2755 self.stream.writeln(
2761 self.stream.writeln(
2756 '%-7s %-7s %-7s %-7s %-7s %s'
2762 '%-7s %-7s %-7s %-7s %-7s %s'
2757 % ('start', 'end', 'cuser', 'csys', 'real', 'Test')
2763 % ('start', 'end', 'cuser', 'csys', 'real', 'Test')
2758 )
2764 )
2759 for tdata in times:
2765 for tdata in times:
2760 test = tdata[0]
2766 test = tdata[0]
2761 cuser, csys, real, start, end = tdata[1:6]
2767 cuser, csys, real, start, end = tdata[1:6]
2762 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
2768 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
2763
2769
2764 @staticmethod
2770 @staticmethod
2765 def _writexunit(result, outf):
2771 def _writexunit(result, outf):
2766 # See http://llg.cubic.org/docs/junit/ for a reference.
2772 # See http://llg.cubic.org/docs/junit/ for a reference.
2767 timesd = {t[0]: t[3] for t in result.times}
2773 timesd = {t[0]: t[3] for t in result.times}
2768 doc = minidom.Document()
2774 doc = minidom.Document()
2769 s = doc.createElement('testsuite')
2775 s = doc.createElement('testsuite')
2770 s.setAttribute('errors', "0") # TODO
2776 s.setAttribute('errors', "0") # TODO
2771 s.setAttribute('failures', str(len(result.failures)))
2777 s.setAttribute('failures', str(len(result.failures)))
2772 s.setAttribute('name', 'run-tests')
2778 s.setAttribute('name', 'run-tests')
2773 s.setAttribute(
2779 s.setAttribute(
2774 'skipped', str(len(result.skipped) + len(result.ignored))
2780 'skipped', str(len(result.skipped) + len(result.ignored))
2775 )
2781 )
2776 s.setAttribute('tests', str(result.testsRun))
2782 s.setAttribute('tests', str(result.testsRun))
2777 doc.appendChild(s)
2783 doc.appendChild(s)
2778 for tc in result.successes:
2784 for tc in result.successes:
2779 t = doc.createElement('testcase')
2785 t = doc.createElement('testcase')
2780 t.setAttribute('name', tc.name)
2786 t.setAttribute('name', tc.name)
2781 tctime = timesd.get(tc.name)
2787 tctime = timesd.get(tc.name)
2782 if tctime is not None:
2788 if tctime is not None:
2783 t.setAttribute('time', '%.3f' % tctime)
2789 t.setAttribute('time', '%.3f' % tctime)
2784 s.appendChild(t)
2790 s.appendChild(t)
2785 for tc, err in sorted(result.faildata.items()):
2791 for tc, err in sorted(result.faildata.items()):
2786 t = doc.createElement('testcase')
2792 t = doc.createElement('testcase')
2787 t.setAttribute('name', tc)
2793 t.setAttribute('name', tc)
2788 tctime = timesd.get(tc)
2794 tctime = timesd.get(tc)
2789 if tctime is not None:
2795 if tctime is not None:
2790 t.setAttribute('time', '%.3f' % tctime)
2796 t.setAttribute('time', '%.3f' % tctime)
2791 # createCDATASection expects a unicode or it will
2797 # createCDATASection expects a unicode or it will
2792 # convert using default conversion rules, which will
2798 # convert using default conversion rules, which will
2793 # fail if string isn't ASCII.
2799 # fail if string isn't ASCII.
2794 err = cdatasafe(err).decode('utf-8', 'replace')
2800 err = cdatasafe(err).decode('utf-8', 'replace')
2795 cd = doc.createCDATASection(err)
2801 cd = doc.createCDATASection(err)
2796 # Use 'failure' here instead of 'error' to match errors = 0,
2802 # Use 'failure' here instead of 'error' to match errors = 0,
2797 # failures = len(result.failures) in the testsuite element.
2803 # failures = len(result.failures) in the testsuite element.
2798 failelem = doc.createElement('failure')
2804 failelem = doc.createElement('failure')
2799 failelem.setAttribute('message', 'output changed')
2805 failelem.setAttribute('message', 'output changed')
2800 failelem.setAttribute('type', 'output-mismatch')
2806 failelem.setAttribute('type', 'output-mismatch')
2801 failelem.appendChild(cd)
2807 failelem.appendChild(cd)
2802 t.appendChild(failelem)
2808 t.appendChild(failelem)
2803 s.appendChild(t)
2809 s.appendChild(t)
2804 for tc, message in result.skipped:
2810 for tc, message in result.skipped:
2805 # According to the schema, 'skipped' has no attributes. So store
2811 # According to the schema, 'skipped' has no attributes. So store
2806 # the skip message as a text node instead.
2812 # the skip message as a text node instead.
2807 t = doc.createElement('testcase')
2813 t = doc.createElement('testcase')
2808 t.setAttribute('name', tc.name)
2814 t.setAttribute('name', tc.name)
2809 binmessage = message.encode('utf-8')
2815 binmessage = message.encode('utf-8')
2810 message = cdatasafe(binmessage).decode('utf-8', 'replace')
2816 message = cdatasafe(binmessage).decode('utf-8', 'replace')
2811 cd = doc.createCDATASection(message)
2817 cd = doc.createCDATASection(message)
2812 skipelem = doc.createElement('skipped')
2818 skipelem = doc.createElement('skipped')
2813 skipelem.appendChild(cd)
2819 skipelem.appendChild(cd)
2814 t.appendChild(skipelem)
2820 t.appendChild(skipelem)
2815 s.appendChild(t)
2821 s.appendChild(t)
2816 outf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
2822 outf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
2817
2823
2818 @staticmethod
2824 @staticmethod
2819 def _writejson(result, outf):
2825 def _writejson(result, outf):
2820 timesd = {}
2826 timesd = {}
2821 for tdata in result.times:
2827 for tdata in result.times:
2822 test = tdata[0]
2828 test = tdata[0]
2823 timesd[test] = tdata[1:]
2829 timesd[test] = tdata[1:]
2824
2830
2825 outcome = {}
2831 outcome = {}
2826 groups = [
2832 groups = [
2827 ('success', ((tc, None) for tc in result.successes)),
2833 ('success', ((tc, None) for tc in result.successes)),
2828 ('failure', result.failures),
2834 ('failure', result.failures),
2829 ('skip', result.skipped),
2835 ('skip', result.skipped),
2830 ]
2836 ]
2831 for res, testcases in groups:
2837 for res, testcases in groups:
2832 for tc, __ in testcases:
2838 for tc, __ in testcases:
2833 if tc.name in timesd:
2839 if tc.name in timesd:
2834 diff = result.faildata.get(tc.name, b'')
2840 diff = result.faildata.get(tc.name, b'')
2835 try:
2841 try:
2836 diff = diff.decode('unicode_escape')
2842 diff = diff.decode('unicode_escape')
2837 except UnicodeDecodeError as e:
2843 except UnicodeDecodeError as e:
2838 diff = '%r decoding diff, sorry' % e
2844 diff = '%r decoding diff, sorry' % e
2839 tres = {
2845 tres = {
2840 'result': res,
2846 'result': res,
2841 'time': ('%0.3f' % timesd[tc.name][2]),
2847 'time': ('%0.3f' % timesd[tc.name][2]),
2842 'cuser': ('%0.3f' % timesd[tc.name][0]),
2848 'cuser': ('%0.3f' % timesd[tc.name][0]),
2843 'csys': ('%0.3f' % timesd[tc.name][1]),
2849 'csys': ('%0.3f' % timesd[tc.name][1]),
2844 'start': ('%0.3f' % timesd[tc.name][3]),
2850 'start': ('%0.3f' % timesd[tc.name][3]),
2845 'end': ('%0.3f' % timesd[tc.name][4]),
2851 'end': ('%0.3f' % timesd[tc.name][4]),
2846 'diff': diff,
2852 'diff': diff,
2847 }
2853 }
2848 else:
2854 else:
2849 # blacklisted test
2855 # blacklisted test
2850 tres = {'result': res}
2856 tres = {'result': res}
2851
2857
2852 outcome[tc.name] = tres
2858 outcome[tc.name] = tres
2853 jsonout = json.dumps(
2859 jsonout = json.dumps(
2854 outcome, sort_keys=True, indent=4, separators=(',', ': ')
2860 outcome, sort_keys=True, indent=4, separators=(',', ': ')
2855 )
2861 )
2856 outf.writelines(("testreport =", jsonout))
2862 outf.writelines(("testreport =", jsonout))
2857
2863
2858
2864
2859 def sorttests(testdescs, previoustimes, shuffle=False):
2865 def sorttests(testdescs, previoustimes, shuffle=False):
2860 """Do an in-place sort of tests."""
2866 """Do an in-place sort of tests."""
2861 if shuffle:
2867 if shuffle:
2862 random.shuffle(testdescs)
2868 random.shuffle(testdescs)
2863 return
2869 return
2864
2870
2865 if previoustimes:
2871 if previoustimes:
2866
2872
2867 def sortkey(f):
2873 def sortkey(f):
2868 f = f['path']
2874 f = f['path']
2869 if f in previoustimes:
2875 if f in previoustimes:
2870 # Use most recent time as estimate
2876 # Use most recent time as estimate
2871 return -(previoustimes[f][-1])
2877 return -(previoustimes[f][-1])
2872 else:
2878 else:
2873 # Default to a rather arbitrary value of 1 second for new tests
2879 # Default to a rather arbitrary value of 1 second for new tests
2874 return -1.0
2880 return -1.0
2875
2881
2876 else:
2882 else:
2877 # keywords for slow tests
2883 # keywords for slow tests
2878 slow = {
2884 slow = {
2879 b'svn': 10,
2885 b'svn': 10,
2880 b'cvs': 10,
2886 b'cvs': 10,
2881 b'hghave': 10,
2887 b'hghave': 10,
2882 b'largefiles-update': 10,
2888 b'largefiles-update': 10,
2883 b'run-tests': 10,
2889 b'run-tests': 10,
2884 b'corruption': 10,
2890 b'corruption': 10,
2885 b'race': 10,
2891 b'race': 10,
2886 b'i18n': 10,
2892 b'i18n': 10,
2887 b'check': 100,
2893 b'check': 100,
2888 b'gendoc': 100,
2894 b'gendoc': 100,
2889 b'contrib-perf': 200,
2895 b'contrib-perf': 200,
2890 b'merge-combination': 100,
2896 b'merge-combination': 100,
2891 }
2897 }
2892 perf = {}
2898 perf = {}
2893
2899
2894 def sortkey(f):
2900 def sortkey(f):
2895 # run largest tests first, as they tend to take the longest
2901 # run largest tests first, as they tend to take the longest
2896 f = f['path']
2902 f = f['path']
2897 try:
2903 try:
2898 return perf[f]
2904 return perf[f]
2899 except KeyError:
2905 except KeyError:
2900 try:
2906 try:
2901 val = -os.stat(f).st_size
2907 val = -os.stat(f).st_size
2902 except OSError as e:
2908 except OSError as e:
2903 if e.errno != errno.ENOENT:
2909 if e.errno != errno.ENOENT:
2904 raise
2910 raise
2905 perf[f] = -1e9 # file does not exist, tell early
2911 perf[f] = -1e9 # file does not exist, tell early
2906 return -1e9
2912 return -1e9
2907 for kw, mul in slow.items():
2913 for kw, mul in slow.items():
2908 if kw in f:
2914 if kw in f:
2909 val *= mul
2915 val *= mul
2910 if f.endswith(b'.py'):
2916 if f.endswith(b'.py'):
2911 val /= 10.0
2917 val /= 10.0
2912 perf[f] = val / 1000.0
2918 perf[f] = val / 1000.0
2913 return perf[f]
2919 return perf[f]
2914
2920
2915 testdescs.sort(key=sortkey)
2921 testdescs.sort(key=sortkey)
2916
2922
2917
2923
2918 class TestRunner(object):
2924 class TestRunner(object):
2919 """Holds context for executing tests.
2925 """Holds context for executing tests.
2920
2926
2921 Tests rely on a lot of state. This object holds it for them.
2927 Tests rely on a lot of state. This object holds it for them.
2922 """
2928 """
2923
2929
2924 # Programs required to run tests.
2930 # Programs required to run tests.
2925 REQUIREDTOOLS = [
2931 REQUIREDTOOLS = [
2926 b'diff',
2932 b'diff',
2927 b'grep',
2933 b'grep',
2928 b'unzip',
2934 b'unzip',
2929 b'gunzip',
2935 b'gunzip',
2930 b'bunzip2',
2936 b'bunzip2',
2931 b'sed',
2937 b'sed',
2932 ]
2938 ]
2933
2939
2934 # Maps file extensions to test class.
2940 # Maps file extensions to test class.
2935 TESTTYPES = [
2941 TESTTYPES = [
2936 (b'.py', PythonTest),
2942 (b'.py', PythonTest),
2937 (b'.t', TTest),
2943 (b'.t', TTest),
2938 ]
2944 ]
2939
2945
2940 def __init__(self):
2946 def __init__(self):
2941 self.options = None
2947 self.options = None
2942 self._hgroot = None
2948 self._hgroot = None
2943 self._testdir = None
2949 self._testdir = None
2944 self._outputdir = None
2950 self._outputdir = None
2945 self._hgtmp = None
2951 self._hgtmp = None
2946 self._installdir = None
2952 self._installdir = None
2947 self._bindir = None
2953 self._bindir = None
2948 self._tmpbinddir = None
2954 self._tmpbinddir = None
2949 self._pythondir = None
2955 self._pythondir = None
2950 self._coveragefile = None
2956 self._coveragefile = None
2951 self._createdfiles = []
2957 self._createdfiles = []
2952 self._hgcommand = None
2958 self._hgcommand = None
2959 self._rhgcommand = None
2953 self._hgpath = None
2960 self._hgpath = None
2954 self._portoffset = 0
2961 self._portoffset = 0
2955 self._ports = {}
2962 self._ports = {}
2956
2963
2957 def run(self, args, parser=None):
2964 def run(self, args, parser=None):
2958 """Run the test suite."""
2965 """Run the test suite."""
2959 oldmask = os.umask(0o22)
2966 oldmask = os.umask(0o22)
2960 try:
2967 try:
2961 parser = parser or getparser()
2968 parser = parser or getparser()
2962 options = parseargs(args, parser)
2969 options = parseargs(args, parser)
2963 tests = [_sys2bytes(a) for a in options.tests]
2970 tests = [_sys2bytes(a) for a in options.tests]
2964 if options.test_list is not None:
2971 if options.test_list is not None:
2965 for listfile in options.test_list:
2972 for listfile in options.test_list:
2966 with open(listfile, 'rb') as f:
2973 with open(listfile, 'rb') as f:
2967 tests.extend(t for t in f.read().splitlines() if t)
2974 tests.extend(t for t in f.read().splitlines() if t)
2968 self.options = options
2975 self.options = options
2969
2976
2970 self._checktools()
2977 self._checktools()
2971 testdescs = self.findtests(tests)
2978 testdescs = self.findtests(tests)
2972 if options.profile_runner:
2979 if options.profile_runner:
2973 import statprof
2980 import statprof
2974
2981
2975 statprof.start()
2982 statprof.start()
2976 result = self._run(testdescs)
2983 result = self._run(testdescs)
2977 if options.profile_runner:
2984 if options.profile_runner:
2978 statprof.stop()
2985 statprof.stop()
2979 statprof.display()
2986 statprof.display()
2980 return result
2987 return result
2981
2988
2982 finally:
2989 finally:
2983 os.umask(oldmask)
2990 os.umask(oldmask)
2984
2991
2985 def _run(self, testdescs):
2992 def _run(self, testdescs):
2986 testdir = getcwdb()
2993 testdir = getcwdb()
2987 self._testdir = osenvironb[b'TESTDIR'] = getcwdb()
2994 self._testdir = osenvironb[b'TESTDIR'] = getcwdb()
2988 # assume all tests in same folder for now
2995 # assume all tests in same folder for now
2989 if testdescs:
2996 if testdescs:
2990 pathname = os.path.dirname(testdescs[0]['path'])
2997 pathname = os.path.dirname(testdescs[0]['path'])
2991 if pathname:
2998 if pathname:
2992 testdir = os.path.join(testdir, pathname)
2999 testdir = os.path.join(testdir, pathname)
2993 self._testdir = osenvironb[b'TESTDIR'] = testdir
3000 self._testdir = osenvironb[b'TESTDIR'] = testdir
2994 if self.options.outputdir:
3001 if self.options.outputdir:
2995 self._outputdir = canonpath(_sys2bytes(self.options.outputdir))
3002 self._outputdir = canonpath(_sys2bytes(self.options.outputdir))
2996 else:
3003 else:
2997 self._outputdir = getcwdb()
3004 self._outputdir = getcwdb()
2998 if testdescs and pathname:
3005 if testdescs and pathname:
2999 self._outputdir = os.path.join(self._outputdir, pathname)
3006 self._outputdir = os.path.join(self._outputdir, pathname)
3000 previoustimes = {}
3007 previoustimes = {}
3001 if self.options.order_by_runtime:
3008 if self.options.order_by_runtime:
3002 previoustimes = dict(loadtimes(self._outputdir))
3009 previoustimes = dict(loadtimes(self._outputdir))
3003 sorttests(testdescs, previoustimes, shuffle=self.options.random)
3010 sorttests(testdescs, previoustimes, shuffle=self.options.random)
3004
3011
3005 if 'PYTHONHASHSEED' not in os.environ:
3012 if 'PYTHONHASHSEED' not in os.environ:
3006 # use a random python hash seed all the time
3013 # use a random python hash seed all the time
3007 # we do the randomness ourself to know what seed is used
3014 # we do the randomness ourself to know what seed is used
3008 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
3015 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
3009
3016
3010 # Rayon (Rust crate for multi-threading) will use all logical CPU cores
3017 # Rayon (Rust crate for multi-threading) will use all logical CPU cores
3011 # by default, causing thrashing on high-cpu-count systems.
3018 # by default, causing thrashing on high-cpu-count systems.
3012 # Setting its limit to 3 during tests should still let us uncover
3019 # Setting its limit to 3 during tests should still let us uncover
3013 # multi-threading bugs while keeping the thrashing reasonable.
3020 # multi-threading bugs while keeping the thrashing reasonable.
3014 os.environ.setdefault("RAYON_NUM_THREADS", "3")
3021 os.environ.setdefault("RAYON_NUM_THREADS", "3")
3015
3022
3016 if self.options.tmpdir:
3023 if self.options.tmpdir:
3017 self.options.keep_tmpdir = True
3024 self.options.keep_tmpdir = True
3018 tmpdir = _sys2bytes(self.options.tmpdir)
3025 tmpdir = _sys2bytes(self.options.tmpdir)
3019 if os.path.exists(tmpdir):
3026 if os.path.exists(tmpdir):
3020 # Meaning of tmpdir has changed since 1.3: we used to create
3027 # Meaning of tmpdir has changed since 1.3: we used to create
3021 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
3028 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
3022 # tmpdir already exists.
3029 # tmpdir already exists.
3023 print("error: temp dir %r already exists" % tmpdir)
3030 print("error: temp dir %r already exists" % tmpdir)
3024 return 1
3031 return 1
3025
3032
3026 os.makedirs(tmpdir)
3033 os.makedirs(tmpdir)
3027 else:
3034 else:
3028 d = None
3035 d = None
3029 if os.name == 'nt':
3036 if os.name == 'nt':
3030 # without this, we get the default temp dir location, but
3037 # without this, we get the default temp dir location, but
3031 # in all lowercase, which causes troubles with paths (issue3490)
3038 # in all lowercase, which causes troubles with paths (issue3490)
3032 d = osenvironb.get(b'TMP', None)
3039 d = osenvironb.get(b'TMP', None)
3033 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
3040 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
3034
3041
3035 self._hgtmp = osenvironb[b'HGTMP'] = os.path.realpath(tmpdir)
3042 self._hgtmp = osenvironb[b'HGTMP'] = os.path.realpath(tmpdir)
3036
3043
3037 if self.options.with_hg:
3044 if self.options.with_hg:
3038 self._installdir = None
3045 self._installdir = None
3039 whg = self.options.with_hg
3046 whg = self.options.with_hg
3040 self._bindir = os.path.dirname(os.path.realpath(whg))
3047 self._bindir = os.path.dirname(os.path.realpath(whg))
3041 assert isinstance(self._bindir, bytes)
3048 assert isinstance(self._bindir, bytes)
3042 self._hgcommand = os.path.basename(whg)
3049 self._hgcommand = os.path.basename(whg)
3043 self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin')
3050 self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin')
3044 os.makedirs(self._tmpbindir)
3051 os.makedirs(self._tmpbindir)
3045
3052
3046 normbin = os.path.normpath(os.path.abspath(whg))
3053 normbin = os.path.normpath(os.path.abspath(whg))
3047 normbin = normbin.replace(_sys2bytes(os.sep), b'/')
3054 normbin = normbin.replace(_sys2bytes(os.sep), b'/')
3048
3055
3049 # Other Python scripts in the test harness need to
3056 # Other Python scripts in the test harness need to
3050 # `import mercurial`. If `hg` is a Python script, we assume
3057 # `import mercurial`. If `hg` is a Python script, we assume
3051 # the Mercurial modules are relative to its path and tell the tests
3058 # the Mercurial modules are relative to its path and tell the tests
3052 # to load Python modules from its directory.
3059 # to load Python modules from its directory.
3053 with open(whg, 'rb') as fh:
3060 with open(whg, 'rb') as fh:
3054 initial = fh.read(1024)
3061 initial = fh.read(1024)
3055
3062
3056 if re.match(b'#!.*python', initial):
3063 if re.match(b'#!.*python', initial):
3057 self._pythondir = self._bindir
3064 self._pythondir = self._bindir
3058 # If it looks like our in-repo Rust binary, use the source root.
3065 # If it looks like our in-repo Rust binary, use the source root.
3059 # This is a bit hacky. But rhg is still not supported outside the
3066 # This is a bit hacky. But rhg is still not supported outside the
3060 # source directory. So until it is, do the simple thing.
3067 # source directory. So until it is, do the simple thing.
3061 elif re.search(b'/rust/target/[^/]+/hg', normbin):
3068 elif re.search(b'/rust/target/[^/]+/hg', normbin):
3062 self._pythondir = os.path.dirname(self._testdir)
3069 self._pythondir = os.path.dirname(self._testdir)
3063 # Fall back to the legacy behavior.
3070 # Fall back to the legacy behavior.
3064 else:
3071 else:
3065 self._pythondir = self._bindir
3072 self._pythondir = self._bindir
3066
3073
3067 else:
3074 else:
3068 self._installdir = os.path.join(self._hgtmp, b"install")
3075 self._installdir = os.path.join(self._hgtmp, b"install")
3069 self._bindir = os.path.join(self._installdir, b"bin")
3076 self._bindir = os.path.join(self._installdir, b"bin")
3070 self._hgcommand = b'hg'
3077 self._hgcommand = b'hg'
3071 self._tmpbindir = self._bindir
3078 self._tmpbindir = self._bindir
3072 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
3079 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
3073
3080
3074 # Force the use of hg.exe instead of relying on MSYS to recognize hg is
3081 # Force the use of hg.exe instead of relying on MSYS to recognize hg is
3075 # a python script and feed it to python.exe. Legacy stdio is force
3082 # a python script and feed it to python.exe. Legacy stdio is force
3076 # enabled by hg.exe, and this is a more realistic way to launch hg
3083 # enabled by hg.exe, and this is a more realistic way to launch hg
3077 # anyway.
3084 # anyway.
3078 if os.name == 'nt' and not self._hgcommand.endswith(b'.exe'):
3085 if os.name == 'nt' and not self._hgcommand.endswith(b'.exe'):
3079 self._hgcommand += b'.exe'
3086 self._hgcommand += b'.exe'
3080
3087
3081 # set CHGHG, then replace "hg" command by "chg"
3088 # set CHGHG, then replace "hg" command by "chg"
3082 chgbindir = self._bindir
3089 chgbindir = self._bindir
3083 if self.options.chg or self.options.with_chg:
3090 if self.options.chg or self.options.with_chg:
3084 osenvironb[b'CHGHG'] = os.path.join(self._bindir, self._hgcommand)
3091 osenvironb[b'CHGHG'] = os.path.join(self._bindir, self._hgcommand)
3085 else:
3092 else:
3086 osenvironb.pop(b'CHGHG', None) # drop flag for hghave
3093 osenvironb.pop(b'CHGHG', None) # drop flag for hghave
3087 if self.options.chg:
3094 if self.options.chg:
3088 self._hgcommand = b'chg'
3095 self._hgcommand = b'chg'
3089 elif self.options.with_chg:
3096 elif self.options.with_chg:
3090 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
3097 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
3091 self._hgcommand = os.path.basename(self.options.with_chg)
3098 self._hgcommand = os.path.basename(self.options.with_chg)
3092
3099
3093 osenvironb[b"BINDIR"] = self._bindir
3100 osenvironb[b"BINDIR"] = self._bindir
3094 osenvironb[b"PYTHON"] = PYTHON
3101 osenvironb[b"PYTHON"] = PYTHON
3095
3102
3096 fileb = _sys2bytes(__file__)
3103 fileb = _sys2bytes(__file__)
3097 runtestdir = os.path.abspath(os.path.dirname(fileb))
3104 runtestdir = os.path.abspath(os.path.dirname(fileb))
3098 osenvironb[b'RUNTESTDIR'] = runtestdir
3105 osenvironb[b'RUNTESTDIR'] = runtestdir
3099 if PYTHON3:
3106 if PYTHON3:
3100 sepb = _sys2bytes(os.pathsep)
3107 sepb = _sys2bytes(os.pathsep)
3101 else:
3108 else:
3102 sepb = os.pathsep
3109 sepb = os.pathsep
3103 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
3110 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
3104 if os.path.islink(__file__):
3111 if os.path.islink(__file__):
3105 # test helper will likely be at the end of the symlink
3112 # test helper will likely be at the end of the symlink
3106 realfile = os.path.realpath(fileb)
3113 realfile = os.path.realpath(fileb)
3107 realdir = os.path.abspath(os.path.dirname(realfile))
3114 realdir = os.path.abspath(os.path.dirname(realfile))
3108 path.insert(2, realdir)
3115 path.insert(2, realdir)
3109 if chgbindir != self._bindir:
3116 if chgbindir != self._bindir:
3110 path.insert(1, chgbindir)
3117 path.insert(1, chgbindir)
3111 if self._testdir != runtestdir:
3118 if self._testdir != runtestdir:
3112 path = [self._testdir] + path
3119 path = [self._testdir] + path
3113 if self._tmpbindir != self._bindir:
3120 if self._tmpbindir != self._bindir:
3114 path = [self._tmpbindir] + path
3121 path = [self._tmpbindir] + path
3115 osenvironb[b"PATH"] = sepb.join(path)
3122 osenvironb[b"PATH"] = sepb.join(path)
3116
3123
3117 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
3124 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
3118 # can run .../tests/run-tests.py test-foo where test-foo
3125 # can run .../tests/run-tests.py test-foo where test-foo
3119 # adds an extension to HGRC. Also include run-test.py directory to
3126 # adds an extension to HGRC. Also include run-test.py directory to
3120 # import modules like heredoctest.
3127 # import modules like heredoctest.
3121 pypath = [self._pythondir, self._testdir, runtestdir]
3128 pypath = [self._pythondir, self._testdir, runtestdir]
3122 # We have to augment PYTHONPATH, rather than simply replacing
3129 # We have to augment PYTHONPATH, rather than simply replacing
3123 # it, in case external libraries are only available via current
3130 # it, in case external libraries are only available via current
3124 # PYTHONPATH. (In particular, the Subversion bindings on OS X
3131 # PYTHONPATH. (In particular, the Subversion bindings on OS X
3125 # are in /opt/subversion.)
3132 # are in /opt/subversion.)
3126 oldpypath = osenvironb.get(IMPL_PATH)
3133 oldpypath = osenvironb.get(IMPL_PATH)
3127 if oldpypath:
3134 if oldpypath:
3128 pypath.append(oldpypath)
3135 pypath.append(oldpypath)
3129 osenvironb[IMPL_PATH] = sepb.join(pypath)
3136 osenvironb[IMPL_PATH] = sepb.join(pypath)
3130
3137
3131 if self.options.pure:
3138 if self.options.pure:
3132 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
3139 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
3133 os.environ["HGMODULEPOLICY"] = "py"
3140 os.environ["HGMODULEPOLICY"] = "py"
3134 if self.options.rust:
3141 if self.options.rust:
3135 os.environ["HGMODULEPOLICY"] = "rust+c"
3142 os.environ["HGMODULEPOLICY"] = "rust+c"
3136 if self.options.no_rust:
3143 if self.options.no_rust:
3137 current_policy = os.environ.get("HGMODULEPOLICY", "")
3144 current_policy = os.environ.get("HGMODULEPOLICY", "")
3138 if current_policy.startswith("rust+"):
3145 if current_policy.startswith("rust+"):
3139 os.environ["HGMODULEPOLICY"] = current_policy[len("rust+") :]
3146 os.environ["HGMODULEPOLICY"] = current_policy[len("rust+") :]
3140 os.environ.pop("HGWITHRUSTEXT", None)
3147 os.environ.pop("HGWITHRUSTEXT", None)
3141
3148
3142 if self.options.allow_slow_tests:
3149 if self.options.allow_slow_tests:
3143 os.environ["HGTEST_SLOW"] = "slow"
3150 os.environ["HGTEST_SLOW"] = "slow"
3144 elif 'HGTEST_SLOW' in os.environ:
3151 elif 'HGTEST_SLOW' in os.environ:
3145 del os.environ['HGTEST_SLOW']
3152 del os.environ['HGTEST_SLOW']
3146
3153
3147 self._coveragefile = os.path.join(self._testdir, b'.coverage')
3154 self._coveragefile = os.path.join(self._testdir, b'.coverage')
3148
3155
3149 if self.options.exceptions:
3156 if self.options.exceptions:
3150 exceptionsdir = os.path.join(self._outputdir, b'exceptions')
3157 exceptionsdir = os.path.join(self._outputdir, b'exceptions')
3151 try:
3158 try:
3152 os.makedirs(exceptionsdir)
3159 os.makedirs(exceptionsdir)
3153 except OSError as e:
3160 except OSError as e:
3154 if e.errno != errno.EEXIST:
3161 if e.errno != errno.EEXIST:
3155 raise
3162 raise
3156
3163
3157 # Remove all existing exception reports.
3164 # Remove all existing exception reports.
3158 for f in os.listdir(exceptionsdir):
3165 for f in os.listdir(exceptionsdir):
3159 os.unlink(os.path.join(exceptionsdir, f))
3166 os.unlink(os.path.join(exceptionsdir, f))
3160
3167
3161 osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir
3168 osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir
3162 logexceptions = os.path.join(self._testdir, b'logexceptions.py')
3169 logexceptions = os.path.join(self._testdir, b'logexceptions.py')
3163 self.options.extra_config_opt.append(
3170 self.options.extra_config_opt.append(
3164 'extensions.logexceptions=%s' % logexceptions.decode('utf-8')
3171 'extensions.logexceptions=%s' % logexceptions.decode('utf-8')
3165 )
3172 )
3166
3173
3167 vlog("# Using TESTDIR", _bytes2sys(self._testdir))
3174 vlog("# Using TESTDIR", _bytes2sys(self._testdir))
3168 vlog("# Using RUNTESTDIR", _bytes2sys(osenvironb[b'RUNTESTDIR']))
3175 vlog("# Using RUNTESTDIR", _bytes2sys(osenvironb[b'RUNTESTDIR']))
3169 vlog("# Using HGTMP", _bytes2sys(self._hgtmp))
3176 vlog("# Using HGTMP", _bytes2sys(self._hgtmp))
3170 vlog("# Using PATH", os.environ["PATH"])
3177 vlog("# Using PATH", os.environ["PATH"])
3171 vlog(
3178 vlog(
3172 "# Using", _bytes2sys(IMPL_PATH), _bytes2sys(osenvironb[IMPL_PATH]),
3179 "# Using", _bytes2sys(IMPL_PATH), _bytes2sys(osenvironb[IMPL_PATH]),
3173 )
3180 )
3174 vlog("# Writing to directory", _bytes2sys(self._outputdir))
3181 vlog("# Writing to directory", _bytes2sys(self._outputdir))
3175
3182
3176 try:
3183 try:
3177 return self._runtests(testdescs) or 0
3184 return self._runtests(testdescs) or 0
3178 finally:
3185 finally:
3179 time.sleep(0.1)
3186 time.sleep(0.1)
3180 self._cleanup()
3187 self._cleanup()
3181
3188
3182 def findtests(self, args):
3189 def findtests(self, args):
3183 """Finds possible test files from arguments.
3190 """Finds possible test files from arguments.
3184
3191
3185 If you wish to inject custom tests into the test harness, this would
3192 If you wish to inject custom tests into the test harness, this would
3186 be a good function to monkeypatch or override in a derived class.
3193 be a good function to monkeypatch or override in a derived class.
3187 """
3194 """
3188 if not args:
3195 if not args:
3189 if self.options.changed:
3196 if self.options.changed:
3190 proc = Popen4(
3197 proc = Popen4(
3191 b'hg st --rev "%s" -man0 .'
3198 b'hg st --rev "%s" -man0 .'
3192 % _sys2bytes(self.options.changed),
3199 % _sys2bytes(self.options.changed),
3193 None,
3200 None,
3194 0,
3201 0,
3195 )
3202 )
3196 stdout, stderr = proc.communicate()
3203 stdout, stderr = proc.communicate()
3197 args = stdout.strip(b'\0').split(b'\0')
3204 args = stdout.strip(b'\0').split(b'\0')
3198 else:
3205 else:
3199 args = os.listdir(b'.')
3206 args = os.listdir(b'.')
3200
3207
3201 expanded_args = []
3208 expanded_args = []
3202 for arg in args:
3209 for arg in args:
3203 if os.path.isdir(arg):
3210 if os.path.isdir(arg):
3204 if not arg.endswith(b'/'):
3211 if not arg.endswith(b'/'):
3205 arg += b'/'
3212 arg += b'/'
3206 expanded_args.extend([arg + a for a in os.listdir(arg)])
3213 expanded_args.extend([arg + a for a in os.listdir(arg)])
3207 else:
3214 else:
3208 expanded_args.append(arg)
3215 expanded_args.append(arg)
3209 args = expanded_args
3216 args = expanded_args
3210
3217
3211 testcasepattern = re.compile(br'([\w-]+\.t|py)(?:#([a-zA-Z0-9_\-.#]+))')
3218 testcasepattern = re.compile(br'([\w-]+\.t|py)(?:#([a-zA-Z0-9_\-.#]+))')
3212 tests = []
3219 tests = []
3213 for t in args:
3220 for t in args:
3214 case = []
3221 case = []
3215
3222
3216 if not (
3223 if not (
3217 os.path.basename(t).startswith(b'test-')
3224 os.path.basename(t).startswith(b'test-')
3218 and (t.endswith(b'.py') or t.endswith(b'.t'))
3225 and (t.endswith(b'.py') or t.endswith(b'.t'))
3219 ):
3226 ):
3220
3227
3221 m = testcasepattern.match(os.path.basename(t))
3228 m = testcasepattern.match(os.path.basename(t))
3222 if m is not None:
3229 if m is not None:
3223 t_basename, casestr = m.groups()
3230 t_basename, casestr = m.groups()
3224 t = os.path.join(os.path.dirname(t), t_basename)
3231 t = os.path.join(os.path.dirname(t), t_basename)
3225 if casestr:
3232 if casestr:
3226 case = casestr.split(b'#')
3233 case = casestr.split(b'#')
3227 else:
3234 else:
3228 continue
3235 continue
3229
3236
3230 if t.endswith(b'.t'):
3237 if t.endswith(b'.t'):
3231 # .t file may contain multiple test cases
3238 # .t file may contain multiple test cases
3232 casedimensions = parsettestcases(t)
3239 casedimensions = parsettestcases(t)
3233 if casedimensions:
3240 if casedimensions:
3234 cases = []
3241 cases = []
3235
3242
3236 def addcases(case, casedimensions):
3243 def addcases(case, casedimensions):
3237 if not casedimensions:
3244 if not casedimensions:
3238 cases.append(case)
3245 cases.append(case)
3239 else:
3246 else:
3240 for c in casedimensions[0]:
3247 for c in casedimensions[0]:
3241 addcases(case + [c], casedimensions[1:])
3248 addcases(case + [c], casedimensions[1:])
3242
3249
3243 addcases([], casedimensions)
3250 addcases([], casedimensions)
3244 if case and case in cases:
3251 if case and case in cases:
3245 cases = [case]
3252 cases = [case]
3246 elif case:
3253 elif case:
3247 # Ignore invalid cases
3254 # Ignore invalid cases
3248 cases = []
3255 cases = []
3249 else:
3256 else:
3250 pass
3257 pass
3251 tests += [{'path': t, 'case': c} for c in sorted(cases)]
3258 tests += [{'path': t, 'case': c} for c in sorted(cases)]
3252 else:
3259 else:
3253 tests.append({'path': t})
3260 tests.append({'path': t})
3254 else:
3261 else:
3255 tests.append({'path': t})
3262 tests.append({'path': t})
3256 return tests
3263 return tests
3257
3264
3258 def _runtests(self, testdescs):
3265 def _runtests(self, testdescs):
3259 def _reloadtest(test, i):
3266 def _reloadtest(test, i):
3260 # convert a test back to its description dict
3267 # convert a test back to its description dict
3261 desc = {'path': test.path}
3268 desc = {'path': test.path}
3262 case = getattr(test, '_case', [])
3269 case = getattr(test, '_case', [])
3263 if case:
3270 if case:
3264 desc['case'] = case
3271 desc['case'] = case
3265 return self._gettest(desc, i)
3272 return self._gettest(desc, i)
3266
3273
3267 try:
3274 try:
3268 if self.options.restart:
3275 if self.options.restart:
3269 orig = list(testdescs)
3276 orig = list(testdescs)
3270 while testdescs:
3277 while testdescs:
3271 desc = testdescs[0]
3278 desc = testdescs[0]
3272 # desc['path'] is a relative path
3279 # desc['path'] is a relative path
3273 if 'case' in desc:
3280 if 'case' in desc:
3274 casestr = b'#'.join(desc['case'])
3281 casestr = b'#'.join(desc['case'])
3275 errpath = b'%s#%s.err' % (desc['path'], casestr)
3282 errpath = b'%s#%s.err' % (desc['path'], casestr)
3276 else:
3283 else:
3277 errpath = b'%s.err' % desc['path']
3284 errpath = b'%s.err' % desc['path']
3278 errpath = os.path.join(self._outputdir, errpath)
3285 errpath = os.path.join(self._outputdir, errpath)
3279 if os.path.exists(errpath):
3286 if os.path.exists(errpath):
3280 break
3287 break
3281 testdescs.pop(0)
3288 testdescs.pop(0)
3282 if not testdescs:
3289 if not testdescs:
3283 print("running all tests")
3290 print("running all tests")
3284 testdescs = orig
3291 testdescs = orig
3285
3292
3286 tests = [self._gettest(d, i) for i, d in enumerate(testdescs)]
3293 tests = [self._gettest(d, i) for i, d in enumerate(testdescs)]
3287 num_tests = len(tests) * self.options.runs_per_test
3294 num_tests = len(tests) * self.options.runs_per_test
3288
3295
3289 jobs = min(num_tests, self.options.jobs)
3296 jobs = min(num_tests, self.options.jobs)
3290
3297
3291 failed = False
3298 failed = False
3292 kws = self.options.keywords
3299 kws = self.options.keywords
3293 if kws is not None and PYTHON3:
3300 if kws is not None and PYTHON3:
3294 kws = kws.encode('utf-8')
3301 kws = kws.encode('utf-8')
3295
3302
3296 suite = TestSuite(
3303 suite = TestSuite(
3297 self._testdir,
3304 self._testdir,
3298 jobs=jobs,
3305 jobs=jobs,
3299 whitelist=self.options.whitelisted,
3306 whitelist=self.options.whitelisted,
3300 blacklist=self.options.blacklist,
3307 blacklist=self.options.blacklist,
3301 retest=self.options.retest,
3308 retest=self.options.retest,
3302 keywords=kws,
3309 keywords=kws,
3303 loop=self.options.loop,
3310 loop=self.options.loop,
3304 runs_per_test=self.options.runs_per_test,
3311 runs_per_test=self.options.runs_per_test,
3305 showchannels=self.options.showchannels,
3312 showchannels=self.options.showchannels,
3306 tests=tests,
3313 tests=tests,
3307 loadtest=_reloadtest,
3314 loadtest=_reloadtest,
3308 )
3315 )
3309 verbosity = 1
3316 verbosity = 1
3310 if self.options.list_tests:
3317 if self.options.list_tests:
3311 verbosity = 0
3318 verbosity = 0
3312 elif self.options.verbose:
3319 elif self.options.verbose:
3313 verbosity = 2
3320 verbosity = 2
3314 runner = TextTestRunner(self, verbosity=verbosity)
3321 runner = TextTestRunner(self, verbosity=verbosity)
3315
3322
3316 if self.options.list_tests:
3323 if self.options.list_tests:
3317 result = runner.listtests(suite)
3324 result = runner.listtests(suite)
3318 else:
3325 else:
3319 if self._installdir:
3326 if self._installdir:
3320 self._installhg()
3327 self._installhg()
3321 self._checkhglib("Testing")
3328 self._checkhglib("Testing")
3322 else:
3329 else:
3323 self._usecorrectpython()
3330 self._usecorrectpython()
3324 if self.options.chg:
3331 if self.options.chg:
3325 assert self._installdir
3332 assert self._installdir
3326 self._installchg()
3333 self._installchg()
3327
3334
3328 log(
3335 log(
3329 'running %d tests using %d parallel processes'
3336 'running %d tests using %d parallel processes'
3330 % (num_tests, jobs)
3337 % (num_tests, jobs)
3331 )
3338 )
3332
3339
3333 result = runner.run(suite)
3340 result = runner.run(suite)
3334
3341
3335 if result.failures or result.errors:
3342 if result.failures or result.errors:
3336 failed = True
3343 failed = True
3337
3344
3338 result.onEnd()
3345 result.onEnd()
3339
3346
3340 if self.options.anycoverage:
3347 if self.options.anycoverage:
3341 self._outputcoverage()
3348 self._outputcoverage()
3342 except KeyboardInterrupt:
3349 except KeyboardInterrupt:
3343 failed = True
3350 failed = True
3344 print("\ninterrupted!")
3351 print("\ninterrupted!")
3345
3352
3346 if failed:
3353 if failed:
3347 return 1
3354 return 1
3348
3355
3349 def _getport(self, count):
3356 def _getport(self, count):
3350 port = self._ports.get(count) # do we have a cached entry?
3357 port = self._ports.get(count) # do we have a cached entry?
3351 if port is None:
3358 if port is None:
3352 portneeded = 3
3359 portneeded = 3
3353 # above 100 tries we just give up and let test reports failure
3360 # above 100 tries we just give up and let test reports failure
3354 for tries in xrange(100):
3361 for tries in xrange(100):
3355 allfree = True
3362 allfree = True
3356 port = self.options.port + self._portoffset
3363 port = self.options.port + self._portoffset
3357 for idx in xrange(portneeded):
3364 for idx in xrange(portneeded):
3358 if not checkportisavailable(port + idx):
3365 if not checkportisavailable(port + idx):
3359 allfree = False
3366 allfree = False
3360 break
3367 break
3361 self._portoffset += portneeded
3368 self._portoffset += portneeded
3362 if allfree:
3369 if allfree:
3363 break
3370 break
3364 self._ports[count] = port
3371 self._ports[count] = port
3365 return port
3372 return port
3366
3373
3367 def _gettest(self, testdesc, count):
3374 def _gettest(self, testdesc, count):
3368 """Obtain a Test by looking at its filename.
3375 """Obtain a Test by looking at its filename.
3369
3376
3370 Returns a Test instance. The Test may not be runnable if it doesn't
3377 Returns a Test instance. The Test may not be runnable if it doesn't
3371 map to a known type.
3378 map to a known type.
3372 """
3379 """
3373 path = testdesc['path']
3380 path = testdesc['path']
3374 lctest = path.lower()
3381 lctest = path.lower()
3375 testcls = Test
3382 testcls = Test
3376
3383
3377 for ext, cls in self.TESTTYPES:
3384 for ext, cls in self.TESTTYPES:
3378 if lctest.endswith(ext):
3385 if lctest.endswith(ext):
3379 testcls = cls
3386 testcls = cls
3380 break
3387 break
3381
3388
3382 refpath = os.path.join(getcwdb(), path)
3389 refpath = os.path.join(getcwdb(), path)
3383 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
3390 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
3384
3391
3385 # extra keyword parameters. 'case' is used by .t tests
3392 # extra keyword parameters. 'case' is used by .t tests
3386 kwds = {k: testdesc[k] for k in ['case'] if k in testdesc}
3393 kwds = {k: testdesc[k] for k in ['case'] if k in testdesc}
3387
3394
3388 t = testcls(
3395 t = testcls(
3389 refpath,
3396 refpath,
3390 self._outputdir,
3397 self._outputdir,
3391 tmpdir,
3398 tmpdir,
3392 keeptmpdir=self.options.keep_tmpdir,
3399 keeptmpdir=self.options.keep_tmpdir,
3393 debug=self.options.debug,
3400 debug=self.options.debug,
3394 first=self.options.first,
3401 first=self.options.first,
3395 timeout=self.options.timeout,
3402 timeout=self.options.timeout,
3396 startport=self._getport(count),
3403 startport=self._getport(count),
3397 extraconfigopts=self.options.extra_config_opt,
3404 extraconfigopts=self.options.extra_config_opt,
3398 shell=self.options.shell,
3405 shell=self.options.shell,
3399 hgcommand=self._hgcommand,
3406 hgcommand=self._hgcommand,
3400 usechg=bool(self.options.with_chg or self.options.chg),
3407 usechg=bool(self.options.with_chg or self.options.chg),
3401 chgdebug=self.options.chg_debug,
3408 chgdebug=self.options.chg_debug,
3402 useipv6=useipv6,
3409 useipv6=useipv6,
3403 **kwds
3410 **kwds
3404 )
3411 )
3405 t.should_reload = True
3412 t.should_reload = True
3406 return t
3413 return t
3407
3414
3408 def _cleanup(self):
3415 def _cleanup(self):
3409 """Clean up state from this test invocation."""
3416 """Clean up state from this test invocation."""
3410 if self.options.keep_tmpdir:
3417 if self.options.keep_tmpdir:
3411 return
3418 return
3412
3419
3413 vlog("# Cleaning up HGTMP", _bytes2sys(self._hgtmp))
3420 vlog("# Cleaning up HGTMP", _bytes2sys(self._hgtmp))
3414 shutil.rmtree(self._hgtmp, True)
3421 shutil.rmtree(self._hgtmp, True)
3415 for f in self._createdfiles:
3422 for f in self._createdfiles:
3416 try:
3423 try:
3417 os.remove(f)
3424 os.remove(f)
3418 except OSError:
3425 except OSError:
3419 pass
3426 pass
3420
3427
3421 def _usecorrectpython(self):
3428 def _usecorrectpython(self):
3422 """Configure the environment to use the appropriate Python in tests."""
3429 """Configure the environment to use the appropriate Python in tests."""
3423 # Tests must use the same interpreter as us or bad things will happen.
3430 # Tests must use the same interpreter as us or bad things will happen.
3424 pyexename = sys.platform == 'win32' and b'python.exe' or b'python'
3431 pyexename = sys.platform == 'win32' and b'python.exe' or b'python'
3425
3432
3426 # os.symlink() is a thing with py3 on Windows, but it requires
3433 # os.symlink() is a thing with py3 on Windows, but it requires
3427 # Administrator rights.
3434 # Administrator rights.
3428 if getattr(os, 'symlink', None) and os.name != 'nt':
3435 if getattr(os, 'symlink', None) and os.name != 'nt':
3429 vlog(
3436 vlog(
3430 "# Making python executable in test path a symlink to '%s'"
3437 "# Making python executable in test path a symlink to '%s'"
3431 % sysexecutable
3438 % sysexecutable
3432 )
3439 )
3433 mypython = os.path.join(self._tmpbindir, pyexename)
3440 mypython = os.path.join(self._tmpbindir, pyexename)
3434 try:
3441 try:
3435 if os.readlink(mypython) == sysexecutable:
3442 if os.readlink(mypython) == sysexecutable:
3436 return
3443 return
3437 os.unlink(mypython)
3444 os.unlink(mypython)
3438 except OSError as err:
3445 except OSError as err:
3439 if err.errno != errno.ENOENT:
3446 if err.errno != errno.ENOENT:
3440 raise
3447 raise
3441 if self._findprogram(pyexename) != sysexecutable:
3448 if self._findprogram(pyexename) != sysexecutable:
3442 try:
3449 try:
3443 os.symlink(sysexecutable, mypython)
3450 os.symlink(sysexecutable, mypython)
3444 self._createdfiles.append(mypython)
3451 self._createdfiles.append(mypython)
3445 except OSError as err:
3452 except OSError as err:
3446 # child processes may race, which is harmless
3453 # child processes may race, which is harmless
3447 if err.errno != errno.EEXIST:
3454 if err.errno != errno.EEXIST:
3448 raise
3455 raise
3449 else:
3456 else:
3450 exedir, exename = os.path.split(sysexecutable)
3457 exedir, exename = os.path.split(sysexecutable)
3451 vlog(
3458 vlog(
3452 "# Modifying search path to find %s as %s in '%s'"
3459 "# Modifying search path to find %s as %s in '%s'"
3453 % (exename, pyexename, exedir)
3460 % (exename, pyexename, exedir)
3454 )
3461 )
3455 path = os.environ['PATH'].split(os.pathsep)
3462 path = os.environ['PATH'].split(os.pathsep)
3456 while exedir in path:
3463 while exedir in path:
3457 path.remove(exedir)
3464 path.remove(exedir)
3458 os.environ['PATH'] = os.pathsep.join([exedir] + path)
3465 os.environ['PATH'] = os.pathsep.join([exedir] + path)
3459 if not self._findprogram(pyexename):
3466 if not self._findprogram(pyexename):
3460 print("WARNING: Cannot find %s in search path" % pyexename)
3467 print("WARNING: Cannot find %s in search path" % pyexename)
3461
3468
3462 def _installhg(self):
3469 def _installhg(self):
3463 """Install hg into the test environment.
3470 """Install hg into the test environment.
3464
3471
3465 This will also configure hg with the appropriate testing settings.
3472 This will also configure hg with the appropriate testing settings.
3466 """
3473 """
3467 vlog("# Performing temporary installation of HG")
3474 vlog("# Performing temporary installation of HG")
3468 installerrs = os.path.join(self._hgtmp, b"install.err")
3475 installerrs = os.path.join(self._hgtmp, b"install.err")
3469 compiler = ''
3476 compiler = ''
3470 if self.options.compiler:
3477 if self.options.compiler:
3471 compiler = '--compiler ' + self.options.compiler
3478 compiler = '--compiler ' + self.options.compiler
3472 setup_opts = b""
3479 setup_opts = b""
3473 if self.options.pure:
3480 if self.options.pure:
3474 setup_opts = b"--pure"
3481 setup_opts = b"--pure"
3475 elif self.options.rust:
3482 elif self.options.rust:
3476 setup_opts = b"--rust"
3483 setup_opts = b"--rust"
3477 elif self.options.no_rust:
3484 elif self.options.no_rust:
3478 setup_opts = b"--no-rust"
3485 setup_opts = b"--no-rust"
3479
3486
3480 # Run installer in hg root
3487 # Run installer in hg root
3481 script = os.path.realpath(sys.argv[0])
3488 script = os.path.realpath(sys.argv[0])
3482 exe = sysexecutable
3489 exe = sysexecutable
3483 if PYTHON3:
3490 if PYTHON3:
3484 compiler = _sys2bytes(compiler)
3491 compiler = _sys2bytes(compiler)
3485 script = _sys2bytes(script)
3492 script = _sys2bytes(script)
3486 exe = _sys2bytes(exe)
3493 exe = _sys2bytes(exe)
3487 hgroot = os.path.dirname(os.path.dirname(script))
3494 hgroot = os.path.dirname(os.path.dirname(script))
3488 self._hgroot = hgroot
3495 self._hgroot = hgroot
3489 os.chdir(hgroot)
3496 os.chdir(hgroot)
3490 nohome = b'--home=""'
3497 nohome = b'--home=""'
3491 if os.name == 'nt':
3498 if os.name == 'nt':
3492 # The --home="" trick works only on OS where os.sep == '/'
3499 # The --home="" trick works only on OS where os.sep == '/'
3493 # because of a distutils convert_path() fast-path. Avoid it at
3500 # because of a distutils convert_path() fast-path. Avoid it at
3494 # least on Windows for now, deal with .pydistutils.cfg bugs
3501 # least on Windows for now, deal with .pydistutils.cfg bugs
3495 # when they happen.
3502 # when they happen.
3496 nohome = b''
3503 nohome = b''
3497 cmd = (
3504 cmd = (
3498 b'"%(exe)s" setup.py %(setup_opts)s clean --all'
3505 b'"%(exe)s" setup.py %(setup_opts)s clean --all'
3499 b' build %(compiler)s --build-base="%(base)s"'
3506 b' build %(compiler)s --build-base="%(base)s"'
3500 b' install --force --prefix="%(prefix)s"'
3507 b' install --force --prefix="%(prefix)s"'
3501 b' --install-lib="%(libdir)s"'
3508 b' --install-lib="%(libdir)s"'
3502 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
3509 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
3503 % {
3510 % {
3504 b'exe': exe,
3511 b'exe': exe,
3505 b'setup_opts': setup_opts,
3512 b'setup_opts': setup_opts,
3506 b'compiler': compiler,
3513 b'compiler': compiler,
3507 b'base': os.path.join(self._hgtmp, b"build"),
3514 b'base': os.path.join(self._hgtmp, b"build"),
3508 b'prefix': self._installdir,
3515 b'prefix': self._installdir,
3509 b'libdir': self._pythondir,
3516 b'libdir': self._pythondir,
3510 b'bindir': self._bindir,
3517 b'bindir': self._bindir,
3511 b'nohome': nohome,
3518 b'nohome': nohome,
3512 b'logfile': installerrs,
3519 b'logfile': installerrs,
3513 }
3520 }
3514 )
3521 )
3515
3522
3516 # setuptools requires install directories to exist.
3523 # setuptools requires install directories to exist.
3517 def makedirs(p):
3524 def makedirs(p):
3518 try:
3525 try:
3519 os.makedirs(p)
3526 os.makedirs(p)
3520 except OSError as e:
3527 except OSError as e:
3521 if e.errno != errno.EEXIST:
3528 if e.errno != errno.EEXIST:
3522 raise
3529 raise
3523
3530
3524 makedirs(self._pythondir)
3531 makedirs(self._pythondir)
3525 makedirs(self._bindir)
3532 makedirs(self._bindir)
3526
3533
3527 vlog("# Running", cmd.decode("utf-8"))
3534 vlog("# Running", cmd.decode("utf-8"))
3528 if subprocess.call(_bytes2sys(cmd), shell=True) == 0:
3535 if subprocess.call(_bytes2sys(cmd), shell=True) == 0:
3529 if not self.options.verbose:
3536 if not self.options.verbose:
3530 try:
3537 try:
3531 os.remove(installerrs)
3538 os.remove(installerrs)
3532 except OSError as e:
3539 except OSError as e:
3533 if e.errno != errno.ENOENT:
3540 if e.errno != errno.ENOENT:
3534 raise
3541 raise
3535 else:
3542 else:
3536 with open(installerrs, 'rb') as f:
3543 with open(installerrs, 'rb') as f:
3537 for line in f:
3544 for line in f:
3538 if PYTHON3:
3545 if PYTHON3:
3539 sys.stdout.buffer.write(line)
3546 sys.stdout.buffer.write(line)
3540 else:
3547 else:
3541 sys.stdout.write(line)
3548 sys.stdout.write(line)
3542 sys.exit(1)
3549 sys.exit(1)
3543 os.chdir(self._testdir)
3550 os.chdir(self._testdir)
3544
3551
3545 self._usecorrectpython()
3552 self._usecorrectpython()
3546
3553
3547 hgbat = os.path.join(self._bindir, b'hg.bat')
3554 hgbat = os.path.join(self._bindir, b'hg.bat')
3548 if os.path.isfile(hgbat):
3555 if os.path.isfile(hgbat):
3549 # hg.bat expects to be put in bin/scripts while run-tests.py
3556 # hg.bat expects to be put in bin/scripts while run-tests.py
3550 # installation layout put it in bin/ directly. Fix it
3557 # installation layout put it in bin/ directly. Fix it
3551 with open(hgbat, 'rb') as f:
3558 with open(hgbat, 'rb') as f:
3552 data = f.read()
3559 data = f.read()
3553 if br'"%~dp0..\python" "%~dp0hg" %*' in data:
3560 if br'"%~dp0..\python" "%~dp0hg" %*' in data:
3554 data = data.replace(
3561 data = data.replace(
3555 br'"%~dp0..\python" "%~dp0hg" %*',
3562 br'"%~dp0..\python" "%~dp0hg" %*',
3556 b'"%~dp0python" "%~dp0hg" %*',
3563 b'"%~dp0python" "%~dp0hg" %*',
3557 )
3564 )
3558 with open(hgbat, 'wb') as f:
3565 with open(hgbat, 'wb') as f:
3559 f.write(data)
3566 f.write(data)
3560 else:
3567 else:
3561 print('WARNING: cannot fix hg.bat reference to python.exe')
3568 print('WARNING: cannot fix hg.bat reference to python.exe')
3562
3569
3563 if self.options.anycoverage:
3570 if self.options.anycoverage:
3564 custom = os.path.join(
3571 custom = os.path.join(
3565 osenvironb[b'RUNTESTDIR'], b'sitecustomize.py'
3572 osenvironb[b'RUNTESTDIR'], b'sitecustomize.py'
3566 )
3573 )
3567 target = os.path.join(self._pythondir, b'sitecustomize.py')
3574 target = os.path.join(self._pythondir, b'sitecustomize.py')
3568 vlog('# Installing coverage trigger to %s' % target)
3575 vlog('# Installing coverage trigger to %s' % target)
3569 shutil.copyfile(custom, target)
3576 shutil.copyfile(custom, target)
3570 rc = os.path.join(self._testdir, b'.coveragerc')
3577 rc = os.path.join(self._testdir, b'.coveragerc')
3571 vlog('# Installing coverage rc to %s' % rc)
3578 vlog('# Installing coverage rc to %s' % rc)
3572 osenvironb[b'COVERAGE_PROCESS_START'] = rc
3579 osenvironb[b'COVERAGE_PROCESS_START'] = rc
3573 covdir = os.path.join(self._installdir, b'..', b'coverage')
3580 covdir = os.path.join(self._installdir, b'..', b'coverage')
3574 try:
3581 try:
3575 os.mkdir(covdir)
3582 os.mkdir(covdir)
3576 except OSError as e:
3583 except OSError as e:
3577 if e.errno != errno.EEXIST:
3584 if e.errno != errno.EEXIST:
3578 raise
3585 raise
3579
3586
3580 osenvironb[b'COVERAGE_DIR'] = covdir
3587 osenvironb[b'COVERAGE_DIR'] = covdir
3581
3588
3582 def _checkhglib(self, verb):
3589 def _checkhglib(self, verb):
3583 """Ensure that the 'mercurial' package imported by python is
3590 """Ensure that the 'mercurial' package imported by python is
3584 the one we expect it to be. If not, print a warning to stderr."""
3591 the one we expect it to be. If not, print a warning to stderr."""
3585 if (self._bindir == self._pythondir) and (
3592 if (self._bindir == self._pythondir) and (
3586 self._bindir != self._tmpbindir
3593 self._bindir != self._tmpbindir
3587 ):
3594 ):
3588 # The pythondir has been inferred from --with-hg flag.
3595 # The pythondir has been inferred from --with-hg flag.
3589 # We cannot expect anything sensible here.
3596 # We cannot expect anything sensible here.
3590 return
3597 return
3591 expecthg = os.path.join(self._pythondir, b'mercurial')
3598 expecthg = os.path.join(self._pythondir, b'mercurial')
3592 actualhg = self._gethgpath()
3599 actualhg = self._gethgpath()
3593 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
3600 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
3594 sys.stderr.write(
3601 sys.stderr.write(
3595 'warning: %s with unexpected mercurial lib: %s\n'
3602 'warning: %s with unexpected mercurial lib: %s\n'
3596 ' (expected %s)\n' % (verb, actualhg, expecthg)
3603 ' (expected %s)\n' % (verb, actualhg, expecthg)
3597 )
3604 )
3598
3605
3599 def _gethgpath(self):
3606 def _gethgpath(self):
3600 """Return the path to the mercurial package that is actually found by
3607 """Return the path to the mercurial package that is actually found by
3601 the current Python interpreter."""
3608 the current Python interpreter."""
3602 if self._hgpath is not None:
3609 if self._hgpath is not None:
3603 return self._hgpath
3610 return self._hgpath
3604
3611
3605 cmd = b'"%s" -c "import mercurial; print (mercurial.__path__[0])"'
3612 cmd = b'"%s" -c "import mercurial; print (mercurial.__path__[0])"'
3606 cmd = cmd % PYTHON
3613 cmd = cmd % PYTHON
3607 if PYTHON3:
3614 if PYTHON3:
3608 cmd = _bytes2sys(cmd)
3615 cmd = _bytes2sys(cmd)
3609
3616
3610 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
3617 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
3611 out, err = p.communicate()
3618 out, err = p.communicate()
3612
3619
3613 self._hgpath = out.strip()
3620 self._hgpath = out.strip()
3614
3621
3615 return self._hgpath
3622 return self._hgpath
3616
3623
3617 def _installchg(self):
3624 def _installchg(self):
3618 """Install chg into the test environment"""
3625 """Install chg into the test environment"""
3619 vlog('# Performing temporary installation of CHG')
3626 vlog('# Performing temporary installation of CHG')
3620 assert os.path.dirname(self._bindir) == self._installdir
3627 assert os.path.dirname(self._bindir) == self._installdir
3621 assert self._hgroot, 'must be called after _installhg()'
3628 assert self._hgroot, 'must be called after _installhg()'
3622 cmd = b'"%(make)s" clean install PREFIX="%(prefix)s"' % {
3629 cmd = b'"%(make)s" clean install PREFIX="%(prefix)s"' % {
3623 b'make': b'make', # TODO: switch by option or environment?
3630 b'make': b'make', # TODO: switch by option or environment?
3624 b'prefix': self._installdir,
3631 b'prefix': self._installdir,
3625 }
3632 }
3626 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
3633 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
3627 vlog("# Running", cmd)
3634 vlog("# Running", cmd)
3628 proc = subprocess.Popen(
3635 proc = subprocess.Popen(
3629 cmd,
3636 cmd,
3630 shell=True,
3637 shell=True,
3631 cwd=cwd,
3638 cwd=cwd,
3632 stdin=subprocess.PIPE,
3639 stdin=subprocess.PIPE,
3633 stdout=subprocess.PIPE,
3640 stdout=subprocess.PIPE,
3634 stderr=subprocess.STDOUT,
3641 stderr=subprocess.STDOUT,
3635 )
3642 )
3636 out, _err = proc.communicate()
3643 out, _err = proc.communicate()
3637 if proc.returncode != 0:
3644 if proc.returncode != 0:
3638 if PYTHON3:
3645 if PYTHON3:
3639 sys.stdout.buffer.write(out)
3646 sys.stdout.buffer.write(out)
3640 else:
3647 else:
3641 sys.stdout.write(out)
3648 sys.stdout.write(out)
3642 sys.exit(1)
3649 sys.exit(1)
3643
3650
3644 def _outputcoverage(self):
3651 def _outputcoverage(self):
3645 """Produce code coverage output."""
3652 """Produce code coverage output."""
3646 import coverage
3653 import coverage
3647
3654
3648 coverage = coverage.coverage
3655 coverage = coverage.coverage
3649
3656
3650 vlog('# Producing coverage report')
3657 vlog('# Producing coverage report')
3651 # chdir is the easiest way to get short, relative paths in the
3658 # chdir is the easiest way to get short, relative paths in the
3652 # output.
3659 # output.
3653 os.chdir(self._hgroot)
3660 os.chdir(self._hgroot)
3654 covdir = os.path.join(_bytes2sys(self._installdir), '..', 'coverage')
3661 covdir = os.path.join(_bytes2sys(self._installdir), '..', 'coverage')
3655 cov = coverage(data_file=os.path.join(covdir, 'cov'))
3662 cov = coverage(data_file=os.path.join(covdir, 'cov'))
3656
3663
3657 # Map install directory paths back to source directory.
3664 # Map install directory paths back to source directory.
3658 cov.config.paths['srcdir'] = ['.', _bytes2sys(self._pythondir)]
3665 cov.config.paths['srcdir'] = ['.', _bytes2sys(self._pythondir)]
3659
3666
3660 cov.combine()
3667 cov.combine()
3661
3668
3662 omit = [
3669 omit = [
3663 _bytes2sys(os.path.join(x, b'*'))
3670 _bytes2sys(os.path.join(x, b'*'))
3664 for x in [self._bindir, self._testdir]
3671 for x in [self._bindir, self._testdir]
3665 ]
3672 ]
3666 cov.report(ignore_errors=True, omit=omit)
3673 cov.report(ignore_errors=True, omit=omit)
3667
3674
3668 if self.options.htmlcov:
3675 if self.options.htmlcov:
3669 htmldir = os.path.join(_bytes2sys(self._outputdir), 'htmlcov')
3676 htmldir = os.path.join(_bytes2sys(self._outputdir), 'htmlcov')
3670 cov.html_report(directory=htmldir, omit=omit)
3677 cov.html_report(directory=htmldir, omit=omit)
3671 if self.options.annotate:
3678 if self.options.annotate:
3672 adir = os.path.join(_bytes2sys(self._outputdir), 'annotated')
3679 adir = os.path.join(_bytes2sys(self._outputdir), 'annotated')
3673 if not os.path.isdir(adir):
3680 if not os.path.isdir(adir):
3674 os.mkdir(adir)
3681 os.mkdir(adir)
3675 cov.annotate(directory=adir, omit=omit)
3682 cov.annotate(directory=adir, omit=omit)
3676
3683
3677 def _findprogram(self, program):
3684 def _findprogram(self, program):
3678 """Search PATH for a executable program"""
3685 """Search PATH for a executable program"""
3679 dpb = _sys2bytes(os.defpath)
3686 dpb = _sys2bytes(os.defpath)
3680 sepb = _sys2bytes(os.pathsep)
3687 sepb = _sys2bytes(os.pathsep)
3681 for p in osenvironb.get(b'PATH', dpb).split(sepb):
3688 for p in osenvironb.get(b'PATH', dpb).split(sepb):
3682 name = os.path.join(p, program)
3689 name = os.path.join(p, program)
3683 if os.name == 'nt' or os.access(name, os.X_OK):
3690 if os.name == 'nt' or os.access(name, os.X_OK):
3684 return name
3691 return name
3685 return None
3692 return None
3686
3693
3687 def _checktools(self):
3694 def _checktools(self):
3688 """Ensure tools required to run tests are present."""
3695 """Ensure tools required to run tests are present."""
3689 for p in self.REQUIREDTOOLS:
3696 for p in self.REQUIREDTOOLS:
3690 if os.name == 'nt' and not p.endswith(b'.exe'):
3697 if os.name == 'nt' and not p.endswith(b'.exe'):
3691 p += b'.exe'
3698 p += b'.exe'
3692 found = self._findprogram(p)
3699 found = self._findprogram(p)
3693 p = p.decode("utf-8")
3700 p = p.decode("utf-8")
3694 if found:
3701 if found:
3695 vlog("# Found prerequisite", p, "at", _bytes2sys(found))
3702 vlog("# Found prerequisite", p, "at", _bytes2sys(found))
3696 else:
3703 else:
3697 print("WARNING: Did not find prerequisite tool: %s " % p)
3704 print("WARNING: Did not find prerequisite tool: %s " % p)
3698
3705
3699
3706
3700 def aggregateexceptions(path):
3707 def aggregateexceptions(path):
3701 exceptioncounts = collections.Counter()
3708 exceptioncounts = collections.Counter()
3702 testsbyfailure = collections.defaultdict(set)
3709 testsbyfailure = collections.defaultdict(set)
3703 failuresbytest = collections.defaultdict(set)
3710 failuresbytest = collections.defaultdict(set)
3704
3711
3705 for f in os.listdir(path):
3712 for f in os.listdir(path):
3706 with open(os.path.join(path, f), 'rb') as fh:
3713 with open(os.path.join(path, f), 'rb') as fh:
3707 data = fh.read().split(b'\0')
3714 data = fh.read().split(b'\0')
3708 if len(data) != 5:
3715 if len(data) != 5:
3709 continue
3716 continue
3710
3717
3711 exc, mainframe, hgframe, hgline, testname = data
3718 exc, mainframe, hgframe, hgline, testname = data
3712 exc = exc.decode('utf-8')
3719 exc = exc.decode('utf-8')
3713 mainframe = mainframe.decode('utf-8')
3720 mainframe = mainframe.decode('utf-8')
3714 hgframe = hgframe.decode('utf-8')
3721 hgframe = hgframe.decode('utf-8')
3715 hgline = hgline.decode('utf-8')
3722 hgline = hgline.decode('utf-8')
3716 testname = testname.decode('utf-8')
3723 testname = testname.decode('utf-8')
3717
3724
3718 key = (hgframe, hgline, exc)
3725 key = (hgframe, hgline, exc)
3719 exceptioncounts[key] += 1
3726 exceptioncounts[key] += 1
3720 testsbyfailure[key].add(testname)
3727 testsbyfailure[key].add(testname)
3721 failuresbytest[testname].add(key)
3728 failuresbytest[testname].add(key)
3722
3729
3723 # Find test having fewest failures for each failure.
3730 # Find test having fewest failures for each failure.
3724 leastfailing = {}
3731 leastfailing = {}
3725 for key, tests in testsbyfailure.items():
3732 for key, tests in testsbyfailure.items():
3726 fewesttest = None
3733 fewesttest = None
3727 fewestcount = 99999999
3734 fewestcount = 99999999
3728 for test in sorted(tests):
3735 for test in sorted(tests):
3729 if len(failuresbytest[test]) < fewestcount:
3736 if len(failuresbytest[test]) < fewestcount:
3730 fewesttest = test
3737 fewesttest = test
3731 fewestcount = len(failuresbytest[test])
3738 fewestcount = len(failuresbytest[test])
3732
3739
3733 leastfailing[key] = (fewestcount, fewesttest)
3740 leastfailing[key] = (fewestcount, fewesttest)
3734
3741
3735 # Create a combined counter so we can sort by total occurrences and
3742 # Create a combined counter so we can sort by total occurrences and
3736 # impacted tests.
3743 # impacted tests.
3737 combined = {}
3744 combined = {}
3738 for key in exceptioncounts:
3745 for key in exceptioncounts:
3739 combined[key] = (
3746 combined[key] = (
3740 exceptioncounts[key],
3747 exceptioncounts[key],
3741 len(testsbyfailure[key]),
3748 len(testsbyfailure[key]),
3742 leastfailing[key][0],
3749 leastfailing[key][0],
3743 leastfailing[key][1],
3750 leastfailing[key][1],
3744 )
3751 )
3745
3752
3746 return {
3753 return {
3747 'exceptioncounts': exceptioncounts,
3754 'exceptioncounts': exceptioncounts,
3748 'total': sum(exceptioncounts.values()),
3755 'total': sum(exceptioncounts.values()),
3749 'combined': combined,
3756 'combined': combined,
3750 'leastfailing': leastfailing,
3757 'leastfailing': leastfailing,
3751 'byfailure': testsbyfailure,
3758 'byfailure': testsbyfailure,
3752 'bytest': failuresbytest,
3759 'bytest': failuresbytest,
3753 }
3760 }
3754
3761
3755
3762
3756 if __name__ == '__main__':
3763 if __name__ == '__main__':
3757 runner = TestRunner()
3764 runner = TestRunner()
3758
3765
3759 try:
3766 try:
3760 import msvcrt
3767 import msvcrt
3761
3768
3762 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
3769 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
3763 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
3770 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
3764 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
3771 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
3765 except ImportError:
3772 except ImportError:
3766 pass
3773 pass
3767
3774
3768 sys.exit(runner.run(sys.argv[1:]))
3775 sys.exit(runner.run(sys.argv[1:]))
General Comments 0
You need to be logged in to leave comments. Login now