2 # SPDX-License-Identifier: GPL-2.0
4 """Tests the `rust_is_available.sh` script.
6 Some of the tests require the real programs to be available in `$PATH`
7 under their canonical name (and with the expected versions).
18 class TestRustIsAvailable(unittest
.TestCase
):
20 class Expected(enum
.Enum
):
22 SUCCESS_WITH_WARNINGS
= enum
.auto()
23 SUCCESS_WITH_EXTRA_OUTPUT
= enum
.auto()
27 def generate_executable(cls
, content
):
28 path
= pathlib
.Path(cls
.tempdir
.name
)
29 name
= str(len(tuple(path
.iterdir())))
31 with
open(path
, "w") as file_
:
33 os
.chmod(path
, os
.stat(path
).st_mode | stat
.S_IXUSR
)
37 def generate_clang(cls
, stdout
):
38 return cls
.generate_executable(f
"""#!/usr/bin/env python3
40 if "-E" in " ".join(sys.argv):
41 print({repr("Clang " + " ".join(cls.llvm_default_version.split(" ")))})
47 def generate_rustc(cls
, stdout
):
48 return cls
.generate_executable(f
"""#!/usr/bin/env python3
50 if "--print sysroot" in " ".join(sys.argv):
51 print({repr(cls.rust_default_sysroot)})
57 def generate_bindgen(cls
, version_stdout
, libclang_stderr
, version_0_66_patched
=False):
58 if libclang_stderr
is None:
59 libclang_case
= f
"raise SystemExit({cls.bindgen_default_bindgen_libclang_failure_exit_code})"
61 libclang_case
= f
"print({repr(libclang_stderr)}, file=sys.stderr)"
63 if version_0_66_patched
:
64 version_0_66_case
= "pass"
66 version_0_66_case
= "raise SystemExit(1)"
68 return cls
.generate_executable(f
"""#!/usr/bin/env python3
70 if "rust_is_available_bindgen_libclang.h" in " ".join(sys.argv):
72 elif "rust_is_available_bindgen_0_66.h" in " ".join(sys.argv):
75 print({repr(version_stdout)})
79 def generate_bindgen_version(cls
, stdout
, version_0_66_patched
=False):
80 return cls
.generate_bindgen(stdout
, cls
.bindgen_default_bindgen_libclang_stderr
, version_0_66_patched
)
83 def generate_bindgen_libclang_failure(cls
):
84 return cls
.generate_bindgen(cls
.bindgen_default_bindgen_version_stdout
, None)
87 def generate_bindgen_libclang(cls
, stderr
):
88 return cls
.generate_bindgen(cls
.bindgen_default_bindgen_version_stdout
, stderr
)
92 cls
.tempdir
= tempfile
.TemporaryDirectory()
94 cls
.missing
= pathlib
.Path(cls
.tempdir
.name
) / "missing"
96 cls
.nonexecutable
= pathlib
.Path(cls
.tempdir
.name
) / "nonexecutable"
97 with
open(cls
.nonexecutable
, "w") as file_
:
98 file_
.write("nonexecutable")
100 cls
.unexpected_binary
= "true"
102 cls
.rustc_default_version
= subprocess
.check_output(("scripts/min-tool-version.sh", "rustc")).decode().strip()
103 cls
.bindgen_default_version
= subprocess
.check_output(("scripts/min-tool-version.sh", "bindgen")).decode().strip()
104 cls
.llvm_default_version
= subprocess
.check_output(("scripts/min-tool-version.sh", "llvm")).decode().strip()
105 cls
.rust_default_sysroot
= subprocess
.check_output(("rustc", "--print", "sysroot")).decode().strip()
107 cls
.bindgen_default_bindgen_version_stdout
= f
"bindgen {cls.bindgen_default_version}"
108 cls
.bindgen_default_bindgen_libclang_failure_exit_code
= 42
109 cls
.bindgen_default_bindgen_libclang_stderr
= f
"scripts/rust_is_available_bindgen_libclang.h:2:9: warning: clang version {cls.llvm_default_version} [-W#pragma-messages], err: false"
111 cls
.default_rustc
= cls
.generate_rustc(f
"rustc {cls.rustc_default_version}")
112 cls
.default_bindgen
= cls
.generate_bindgen(cls
.bindgen_default_bindgen_version_stdout
, cls
.bindgen_default_bindgen_libclang_stderr
)
113 cls
.default_cc
= cls
.generate_clang(f
"clang version {cls.llvm_default_version}")
115 def run_script(self
, expected
, override_env
):
117 "RUSTC": self
.default_rustc
,
118 "BINDGEN": self
.default_bindgen
,
119 "CC": self
.default_cc
,
122 for key
, value
in override_env
.items():
128 result
= subprocess
.run("scripts/rust_is_available.sh", env
=env
, capture_output
=True)
130 # The script should never output anything to `stdout`.
131 self
.assertEqual(result
.stdout
, b
"")
133 if expected
== self
.Expected
.SUCCESS
:
134 # When expecting a success, the script should return 0
135 # and it should not output anything to `stderr`.
136 self
.assertEqual(result
.returncode
, 0)
137 self
.assertEqual(result
.stderr
, b
"")
138 elif expected
== self
.Expected
.SUCCESS_WITH_EXTRA_OUTPUT
:
139 # When expecting a success with extra output (that is not warnings,
140 # which is the common case), the script should return 0 and it
141 # should output at least something to `stderr` (the output should
142 # be checked further by the test).
143 self
.assertEqual(result
.returncode
, 0)
144 self
.assertNotEqual(result
.stderr
, b
"")
145 elif expected
== self
.Expected
.SUCCESS_WITH_WARNINGS
:
146 # When expecting a success with warnings, the script should return 0
147 # and it should output at least the instructions to `stderr`.
148 self
.assertEqual(result
.returncode
, 0)
149 self
.assertIn(b
"Please see Documentation/rust/quick-start.rst for details", result
.stderr
)
151 # When expecting a failure, the script should return non-0
152 # and it should output at least the instructions to `stderr`.
153 self
.assertNotEqual(result
.returncode
, 0)
154 self
.assertIn(b
"Please see Documentation/rust/quick-start.rst for details", result
.stderr
)
156 # The output will generally be UTF-8 (i.e. unless the user has
157 # put strange values in the environment).
158 result
.stderr
= result
.stderr
.decode()
162 def test_rustc_unset(self
):
163 result
= self
.run_script(self
.Expected
.FAILURE
, { "RUSTC": None })
164 self
.assertIn("Environment variable 'RUSTC' is not set.", result
.stderr
)
165 self
.assertIn("This script is intended to be called from Kbuild.", result
.stderr
)
167 def test_bindgen_unset(self
):
168 result
= self
.run_script(self
.Expected
.FAILURE
, { "BINDGEN": None })
169 self
.assertIn("Environment variable 'BINDGEN' is not set.", result
.stderr
)
170 self
.assertIn("This script is intended to be called from Kbuild.", result
.stderr
)
172 def test_cc_unset(self
):
173 result
= self
.run_script(self
.Expected
.FAILURE
, { "CC": None })
174 self
.assertIn("Environment variable 'CC' is not set.", result
.stderr
)
175 self
.assertIn("This script is intended to be called from Kbuild.", result
.stderr
)
177 def test_rustc_missing(self
):
178 result
= self
.run_script(self
.Expected
.FAILURE
, { "RUSTC": self
.missing
})
179 self
.assertIn(f
"Rust compiler '{self.missing}' could not be found.", result
.stderr
)
181 def test_bindgen_missing(self
):
182 result
= self
.run_script(self
.Expected
.FAILURE
, { "BINDGEN": self
.missing
})
183 self
.assertIn(f
"Rust bindings generator '{self.missing}' could not be found.", result
.stderr
)
185 def test_rustc_nonexecutable(self
):
186 result
= self
.run_script(self
.Expected
.FAILURE
, { "RUSTC": self
.nonexecutable
})
187 self
.assertIn(f
"Running '{self.nonexecutable}' to check the Rust compiler version failed with", result
.stderr
)
189 def test_rustc_unexpected_binary(self
):
190 result
= self
.run_script(self
.Expected
.FAILURE
, { "RUSTC": self
.unexpected_binary
})
191 self
.assertIn(f
"Running '{self.unexpected_binary}' to check the Rust compiler version did not return", result
.stderr
)
193 def test_rustc_unexpected_name(self
):
194 rustc
= self
.generate_rustc(f
"unexpected {self.rustc_default_version} (a8314ef7d 2022-06-27)")
195 result
= self
.run_script(self
.Expected
.FAILURE
, { "RUSTC": rustc
})
196 self
.assertIn(f
"Running '{rustc}' to check the Rust compiler version did not return", result
.stderr
)
198 def test_rustc_unexpected_version(self
):
199 rustc
= self
.generate_rustc("rustc unexpected (a8314ef7d 2022-06-27)")
200 result
= self
.run_script(self
.Expected
.FAILURE
, { "RUSTC": rustc
})
201 self
.assertIn(f
"Running '{rustc}' to check the Rust compiler version did not return", result
.stderr
)
203 def test_rustc_no_minor(self
):
204 rustc
= self
.generate_rustc(f
"rustc {'.'.join(self.rustc_default_version.split('.')[:2])} (a8314ef7d 2022-06-27)")
205 result
= self
.run_script(self
.Expected
.FAILURE
, { "RUSTC": rustc
})
206 self
.assertIn(f
"Running '{rustc}' to check the Rust compiler version did not return", result
.stderr
)
208 def test_rustc_old_version(self
):
209 rustc
= self
.generate_rustc("rustc 1.60.0 (a8314ef7d 2022-06-27)")
210 result
= self
.run_script(self
.Expected
.FAILURE
, { "RUSTC": rustc
})
211 self
.assertIn(f
"Rust compiler '{rustc}' is too old.", result
.stderr
)
213 def test_bindgen_nonexecutable(self
):
214 result
= self
.run_script(self
.Expected
.FAILURE
, { "BINDGEN": self
.nonexecutable
})
215 self
.assertIn(f
"Running '{self.nonexecutable}' to check the Rust bindings generator version failed with", result
.stderr
)
217 def test_bindgen_unexpected_binary(self
):
218 result
= self
.run_script(self
.Expected
.FAILURE
, { "BINDGEN": self
.unexpected_binary
})
219 self
.assertIn(f
"Running '{self.unexpected_binary}' to check the bindings generator version did not return", result
.stderr
)
221 def test_bindgen_unexpected_name(self
):
222 bindgen
= self
.generate_bindgen_version(f
"unexpected {self.bindgen_default_version}")
223 result
= self
.run_script(self
.Expected
.FAILURE
, { "BINDGEN": bindgen
})
224 self
.assertIn(f
"Running '{bindgen}' to check the bindings generator version did not return", result
.stderr
)
226 def test_bindgen_unexpected_version(self
):
227 bindgen
= self
.generate_bindgen_version("bindgen unexpected")
228 result
= self
.run_script(self
.Expected
.FAILURE
, { "BINDGEN": bindgen
})
229 self
.assertIn(f
"Running '{bindgen}' to check the bindings generator version did not return", result
.stderr
)
231 def test_bindgen_no_minor(self
):
232 bindgen
= self
.generate_bindgen_version(f
"bindgen {'.'.join(self.bindgen_default_version.split('.')[:2])}")
233 result
= self
.run_script(self
.Expected
.FAILURE
, { "BINDGEN": bindgen
})
234 self
.assertIn(f
"Running '{bindgen}' to check the bindings generator version did not return", result
.stderr
)
236 def test_bindgen_old_version(self
):
237 bindgen
= self
.generate_bindgen_version("bindgen 0.50.0")
238 result
= self
.run_script(self
.Expected
.FAILURE
, { "BINDGEN": bindgen
})
239 self
.assertIn(f
"Rust bindings generator '{bindgen}' is too old.", result
.stderr
)
241 def test_bindgen_bad_version_0_66_0_and_0_66_1(self
):
242 for version
in ("0.66.0", "0.66.1"):
243 with self
.subTest(version
=version
):
244 bindgen
= self
.generate_bindgen_version(f
"bindgen {version}")
245 result
= self
.run_script(self
.Expected
.SUCCESS_WITH_WARNINGS
, { "BINDGEN": bindgen
})
246 self
.assertIn(f
"Rust bindings generator '{bindgen}' versions 0.66.0 and 0.66.1 may not", result
.stderr
)
248 def test_bindgen_bad_version_0_66_0_and_0_66_1_patched(self
):
249 for version
in ("0.66.0", "0.66.1"):
250 with self
.subTest(version
=version
):
251 bindgen
= self
.generate_bindgen_version(f
"bindgen {version}", True)
252 result
= self
.run_script(self
.Expected
.SUCCESS
, { "BINDGEN": bindgen
})
254 def test_bindgen_libclang_failure(self
):
255 bindgen
= self
.generate_bindgen_libclang_failure()
256 result
= self
.run_script(self
.Expected
.FAILURE
, { "BINDGEN": bindgen
})
257 self
.assertIn(f
"Running '{bindgen}' to check the libclang version (used by the Rust", result
.stderr
)
258 self
.assertIn(f
"bindings generator) failed with code {self.bindgen_default_bindgen_libclang_failure_exit_code}. This may be caused by", result
.stderr
)
260 def test_bindgen_libclang_unexpected_version(self
):
261 bindgen
= self
.generate_bindgen_libclang("scripts/rust_is_available_bindgen_libclang.h:2:9: warning: clang version unexpected [-W#pragma-messages], err: false")
262 result
= self
.run_script(self
.Expected
.FAILURE
, { "BINDGEN": bindgen
})
263 self
.assertIn(f
"Running '{bindgen}' to check the libclang version (used by the Rust", result
.stderr
)
264 self
.assertIn("bindings generator) did not return an expected output. See output", result
.stderr
)
266 def test_bindgen_libclang_old_version(self
):
267 bindgen
= self
.generate_bindgen_libclang("scripts/rust_is_available_bindgen_libclang.h:2:9: warning: clang version 10.0.0 [-W#pragma-messages], err: false")
268 result
= self
.run_script(self
.Expected
.FAILURE
, { "BINDGEN": bindgen
})
269 self
.assertIn(f
"libclang (used by the Rust bindings generator '{bindgen}') is too old.", result
.stderr
)
271 def test_clang_matches_bindgen_libclang_different_bindgen(self
):
272 bindgen
= self
.generate_bindgen_libclang("scripts/rust_is_available_bindgen_libclang.h:2:9: warning: clang version 999.0.0 [-W#pragma-messages], err: false")
273 result
= self
.run_script(self
.Expected
.SUCCESS_WITH_WARNINGS
, { "BINDGEN": bindgen
})
274 self
.assertIn("version does not match Clang's. This may be a problem.", result
.stderr
)
276 def test_clang_matches_bindgen_libclang_different_clang(self
):
277 cc
= self
.generate_clang("clang version 999.0.0")
278 result
= self
.run_script(self
.Expected
.SUCCESS_WITH_WARNINGS
, { "CC": cc
})
279 self
.assertIn("version does not match Clang's. This may be a problem.", result
.stderr
)
281 def test_rustc_src_core_krustflags(self
):
282 result
= self
.run_script(self
.Expected
.FAILURE
, { "PATH": os
.environ
["PATH"], "RUSTC": "rustc", "KRUSTFLAGS": f
"--sysroot={self.missing}" })
283 self
.assertIn("Source code for the 'core' standard library could not be found", result
.stderr
)
285 def test_rustc_src_core_rustlibsrc(self
):
286 result
= self
.run_script(self
.Expected
.FAILURE
, { "RUST_LIB_SRC": self
.missing
})
287 self
.assertIn("Source code for the 'core' standard library could not be found", result
.stderr
)
289 def test_success_cc_unknown(self
):
290 result
= self
.run_script(self
.Expected
.SUCCESS_WITH_EXTRA_OUTPUT
, { "CC": self
.missing
})
291 self
.assertIn("unknown C compiler", result
.stderr
)
293 def test_success_cc_multiple_arguments_ccache(self
):
294 clang
= self
.generate_clang(f
"""Ubuntu clang version {self.llvm_default_version}-1ubuntu1
295 Target: x86_64-pc-linux-gnu
297 InstalledDir: /usr/bin
299 result
= self
.run_script(self
.Expected
.SUCCESS
, { "CC": f
"{clang} clang" })
301 def test_success_rustc_version(self
):
302 for rustc_stdout
in (
303 f
"rustc {self.rustc_default_version} (a8314ef7d 2022-06-27)",
304 f
"rustc {self.rustc_default_version}-dev (a8314ef7d 2022-06-27)",
305 f
"rustc {self.rustc_default_version}-1.60.0 (a8314ef7d 2022-06-27)",
307 with self
.subTest(rustc_stdout
=rustc_stdout
):
308 rustc
= self
.generate_rustc(rustc_stdout
)
309 result
= self
.run_script(self
.Expected
.SUCCESS
, { "RUSTC": rustc
})
311 def test_success_bindgen_version(self
):
312 for bindgen_stdout
in (
313 f
"bindgen {self.bindgen_default_version}",
314 f
"bindgen {self.bindgen_default_version}-dev",
315 f
"bindgen {self.bindgen_default_version}-0.999.0",
317 with self
.subTest(bindgen_stdout
=bindgen_stdout
):
318 bindgen
= self
.generate_bindgen_version(bindgen_stdout
)
319 result
= self
.run_script(self
.Expected
.SUCCESS
, { "BINDGEN": bindgen
})
321 def test_success_bindgen_libclang(self
):
323 f
"scripts/rust_is_available_bindgen_libclang.h:2:9: warning: clang version {self.llvm_default_version} (https://github.com/llvm/llvm-project.git 4a2c05b05ed07f1f620e94f6524a8b4b2760a0b1) [-W#pragma-messages], err: false",
324 f
"/home/jd/Documents/dev/kernel-module-flake/linux-6.1/outputs/dev/lib/modules/6.1.0-development/source/scripts/rust_is_available_bindgen_libclang.h:2:9: warning: clang version {self.llvm_default_version} [-W#pragma-messages], err: false",
325 f
"scripts/rust_is_available_bindgen_libclang.h:2:9: warning: clang version {self.llvm_default_version} (Fedora 13.0.0-3.fc35) [-W#pragma-messages], err: false",
327 /nix/store/dsd5gz46hdbdk2rfdimqddhq6m8m8fqs-bash-5.1-p16/bin/bash: warning: setlocale: LC_ALL: cannot change locale (c)
328 scripts/rust_is_available_bindgen_libclang.h:2:9: warning: clang version {self.llvm_default_version} [-W#pragma-messages], err: false
331 /nix/store/dsd5gz46hdbdk2rfdimqddhq6m8m8fqs-bash-5.1.0-p16/bin/bash: warning: setlocale: LC_ALL: cannot change locale (c)
332 /home/jd/Documents/dev/kernel-module-flake/linux-6.1/outputs/dev/lib/modules/6.1.0-development/source/scripts/rust_is_available_bindgen_libclang.h:2:9: warning: clang version {self.llvm_default_version} (Fedora 13.0.0-3.fc35) [-W#pragma-messages], err: false
335 with self
.subTest(stderr
=stderr
):
336 bindgen
= self
.generate_bindgen_libclang(stderr
)
337 result
= self
.run_script(self
.Expected
.SUCCESS
, { "BINDGEN": bindgen
})
339 def test_success_clang_version(self
):
340 for clang_stdout
in (
341 f
"clang version {self.llvm_default_version} (https://github.com/llvm/llvm-project.git 4a2c05b05ed07f1f620e94f6524a8b4b2760a0b1)",
342 f
"clang version {self.llvm_default_version}-dev",
343 f
"clang version {self.llvm_default_version}-2~ubuntu20.04.1",
344 f
"Ubuntu clang version {self.llvm_default_version}-2~ubuntu20.04.1",
346 with self
.subTest(clang_stdout
=clang_stdout
):
347 clang
= self
.generate_clang(clang_stdout
)
348 result
= self
.run_script(self
.Expected
.SUCCESS
, { "CC": clang
})
350 def test_success_real_programs(self
):
351 for cc
in ["gcc", "clang"]:
352 with self
.subTest(cc
=cc
):
353 result
= self
.run_script(self
.Expected
.SUCCESS
, {
354 "PATH": os
.environ
["PATH"],
356 "BINDGEN": "bindgen",
360 if __name__
== "__main__":