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, libclang_concat_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 if libclang_concat_patched
:
69 libclang_concat_case
= "print('pub static mut foofoo: ::std::os::raw::c_int;')"
71 libclang_concat_case
= "pass"
73 return cls
.generate_executable(f
"""#!/usr/bin/env python3
75 if "rust_is_available_bindgen_libclang.h" in " ".join(sys.argv):
77 elif "rust_is_available_bindgen_0_66.h" in " ".join(sys.argv):
79 elif "rust_is_available_bindgen_libclang_concat.h" in " ".join(sys.argv):
80 {libclang_concat_case}
82 print({repr(version_stdout)})
86 def generate_bindgen_version(cls
, stdout
, version_0_66_patched
=False):
87 return cls
.generate_bindgen(stdout
, cls
.bindgen_default_bindgen_libclang_stderr
, version_0_66_patched
)
90 def generate_bindgen_libclang_failure(cls
):
91 return cls
.generate_bindgen(cls
.bindgen_default_bindgen_version_stdout
, None)
94 def generate_bindgen_libclang(cls
, stderr
):
95 return cls
.generate_bindgen(cls
.bindgen_default_bindgen_version_stdout
, stderr
)
99 cls
.tempdir
= tempfile
.TemporaryDirectory()
101 cls
.missing
= pathlib
.Path(cls
.tempdir
.name
) / "missing"
103 cls
.nonexecutable
= pathlib
.Path(cls
.tempdir
.name
) / "nonexecutable"
104 with
open(cls
.nonexecutable
, "w") as file_
:
105 file_
.write("nonexecutable")
107 cls
.unexpected_binary
= "true"
109 cls
.rustc_default_version
= subprocess
.check_output(("scripts/min-tool-version.sh", "rustc")).decode().strip()
110 cls
.bindgen_default_version
= subprocess
.check_output(("scripts/min-tool-version.sh", "bindgen")).decode().strip()
111 cls
.llvm_default_version
= subprocess
.check_output(("scripts/min-tool-version.sh", "llvm")).decode().strip()
112 cls
.rust_default_sysroot
= subprocess
.check_output(("rustc", "--print", "sysroot")).decode().strip()
114 cls
.bindgen_default_bindgen_version_stdout
= f
"bindgen {cls.bindgen_default_version}"
115 cls
.bindgen_default_bindgen_libclang_failure_exit_code
= 42
116 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"
118 cls
.default_rustc
= cls
.generate_rustc(f
"rustc {cls.rustc_default_version}")
119 cls
.default_bindgen
= cls
.generate_bindgen(cls
.bindgen_default_bindgen_version_stdout
, cls
.bindgen_default_bindgen_libclang_stderr
)
120 cls
.default_cc
= cls
.generate_clang(f
"clang version {cls.llvm_default_version}")
122 def run_script(self
, expected
, override_env
):
124 "RUSTC": self
.default_rustc
,
125 "BINDGEN": self
.default_bindgen
,
126 "CC": self
.default_cc
,
129 for key
, value
in override_env
.items():
135 result
= subprocess
.run("scripts/rust_is_available.sh", env
=env
, capture_output
=True)
137 # The script should never output anything to `stdout`.
138 self
.assertEqual(result
.stdout
, b
"")
140 if expected
== self
.Expected
.SUCCESS
:
141 # When expecting a success, the script should return 0
142 # and it should not output anything to `stderr`.
143 self
.assertEqual(result
.returncode
, 0)
144 self
.assertEqual(result
.stderr
, b
"")
145 elif expected
== self
.Expected
.SUCCESS_WITH_EXTRA_OUTPUT
:
146 # When expecting a success with extra output (that is not warnings,
147 # which is the common case), the script should return 0 and it
148 # should output at least something to `stderr` (the output should
149 # be checked further by the test).
150 self
.assertEqual(result
.returncode
, 0)
151 self
.assertNotEqual(result
.stderr
, b
"")
152 elif expected
== self
.Expected
.SUCCESS_WITH_WARNINGS
:
153 # When expecting a success with warnings, the script should return 0
154 # and it should output at least the instructions to `stderr`.
155 self
.assertEqual(result
.returncode
, 0)
156 self
.assertIn(b
"Please see Documentation/rust/quick-start.rst for details", result
.stderr
)
158 # When expecting a failure, the script should return non-0
159 # and it should output at least the instructions to `stderr`.
160 self
.assertNotEqual(result
.returncode
, 0)
161 self
.assertIn(b
"Please see Documentation/rust/quick-start.rst for details", result
.stderr
)
163 # The output will generally be UTF-8 (i.e. unless the user has
164 # put strange values in the environment).
165 result
.stderr
= result
.stderr
.decode()
169 def test_rustc_unset(self
):
170 result
= self
.run_script(self
.Expected
.FAILURE
, { "RUSTC": None })
171 self
.assertIn("Environment variable 'RUSTC' is not set.", result
.stderr
)
172 self
.assertIn("This script is intended to be called from Kbuild.", result
.stderr
)
174 def test_bindgen_unset(self
):
175 result
= self
.run_script(self
.Expected
.FAILURE
, { "BINDGEN": None })
176 self
.assertIn("Environment variable 'BINDGEN' is not set.", result
.stderr
)
177 self
.assertIn("This script is intended to be called from Kbuild.", result
.stderr
)
179 def test_cc_unset(self
):
180 result
= self
.run_script(self
.Expected
.FAILURE
, { "CC": None })
181 self
.assertIn("Environment variable 'CC' is not set.", result
.stderr
)
182 self
.assertIn("This script is intended to be called from Kbuild.", result
.stderr
)
184 def test_rustc_missing(self
):
185 result
= self
.run_script(self
.Expected
.FAILURE
, { "RUSTC": self
.missing
})
186 self
.assertIn(f
"Rust compiler '{self.missing}' could not be found.", result
.stderr
)
188 def test_bindgen_missing(self
):
189 result
= self
.run_script(self
.Expected
.FAILURE
, { "BINDGEN": self
.missing
})
190 self
.assertIn(f
"Rust bindings generator '{self.missing}' could not be found.", result
.stderr
)
192 def test_rustc_nonexecutable(self
):
193 result
= self
.run_script(self
.Expected
.FAILURE
, { "RUSTC": self
.nonexecutable
})
194 self
.assertIn(f
"Running '{self.nonexecutable}' to check the Rust compiler version failed with", result
.stderr
)
196 def test_rustc_unexpected_binary(self
):
197 result
= self
.run_script(self
.Expected
.FAILURE
, { "RUSTC": self
.unexpected_binary
})
198 self
.assertIn(f
"Running '{self.unexpected_binary}' to check the Rust compiler version did not return", result
.stderr
)
200 def test_rustc_unexpected_name(self
):
201 rustc
= self
.generate_rustc(f
"unexpected {self.rustc_default_version} (a8314ef7d 2022-06-27)")
202 result
= self
.run_script(self
.Expected
.FAILURE
, { "RUSTC": rustc
})
203 self
.assertIn(f
"Running '{rustc}' to check the Rust compiler version did not return", result
.stderr
)
205 def test_rustc_unexpected_version(self
):
206 rustc
= self
.generate_rustc("rustc unexpected (a8314ef7d 2022-06-27)")
207 result
= self
.run_script(self
.Expected
.FAILURE
, { "RUSTC": rustc
})
208 self
.assertIn(f
"Running '{rustc}' to check the Rust compiler version did not return", result
.stderr
)
210 def test_rustc_no_minor(self
):
211 rustc
= self
.generate_rustc(f
"rustc {'.'.join(self.rustc_default_version.split('.')[:2])} (a8314ef7d 2022-06-27)")
212 result
= self
.run_script(self
.Expected
.FAILURE
, { "RUSTC": rustc
})
213 self
.assertIn(f
"Running '{rustc}' to check the Rust compiler version did not return", result
.stderr
)
215 def test_rustc_old_version(self
):
216 rustc
= self
.generate_rustc("rustc 1.60.0 (a8314ef7d 2022-06-27)")
217 result
= self
.run_script(self
.Expected
.FAILURE
, { "RUSTC": rustc
})
218 self
.assertIn(f
"Rust compiler '{rustc}' is too old.", result
.stderr
)
220 def test_bindgen_nonexecutable(self
):
221 result
= self
.run_script(self
.Expected
.FAILURE
, { "BINDGEN": self
.nonexecutable
})
222 self
.assertIn(f
"Running '{self.nonexecutable}' to check the Rust bindings generator version failed with", result
.stderr
)
224 def test_bindgen_unexpected_binary(self
):
225 result
= self
.run_script(self
.Expected
.FAILURE
, { "BINDGEN": self
.unexpected_binary
})
226 self
.assertIn(f
"Running '{self.unexpected_binary}' to check the bindings generator version did not return", result
.stderr
)
228 def test_bindgen_unexpected_name(self
):
229 bindgen
= self
.generate_bindgen_version(f
"unexpected {self.bindgen_default_version}")
230 result
= self
.run_script(self
.Expected
.FAILURE
, { "BINDGEN": bindgen
})
231 self
.assertIn(f
"Running '{bindgen}' to check the bindings generator version did not return", result
.stderr
)
233 def test_bindgen_unexpected_version(self
):
234 bindgen
= self
.generate_bindgen_version("bindgen unexpected")
235 result
= self
.run_script(self
.Expected
.FAILURE
, { "BINDGEN": bindgen
})
236 self
.assertIn(f
"Running '{bindgen}' to check the bindings generator version did not return", result
.stderr
)
238 def test_bindgen_no_minor(self
):
239 bindgen
= self
.generate_bindgen_version(f
"bindgen {'.'.join(self.bindgen_default_version.split('.')[:2])}")
240 result
= self
.run_script(self
.Expected
.FAILURE
, { "BINDGEN": bindgen
})
241 self
.assertIn(f
"Running '{bindgen}' to check the bindings generator version did not return", result
.stderr
)
243 def test_bindgen_old_version(self
):
244 bindgen
= self
.generate_bindgen_version("bindgen 0.50.0")
245 result
= self
.run_script(self
.Expected
.FAILURE
, { "BINDGEN": bindgen
})
246 self
.assertIn(f
"Rust bindings generator '{bindgen}' is too old.", result
.stderr
)
248 def test_bindgen_bad_version_0_66_0_and_0_66_1(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}")
252 result
= self
.run_script(self
.Expected
.SUCCESS_WITH_WARNINGS
, { "BINDGEN": bindgen
})
253 self
.assertIn(f
"Rust bindings generator '{bindgen}' versions 0.66.0 and 0.66.1 may not", result
.stderr
)
255 def test_bindgen_bad_version_0_66_0_and_0_66_1_patched(self
):
256 for version
in ("0.66.0", "0.66.1"):
257 with self
.subTest(version
=version
):
258 bindgen
= self
.generate_bindgen_version(f
"bindgen {version}", True)
259 result
= self
.run_script(self
.Expected
.SUCCESS
, { "BINDGEN": bindgen
})
261 def test_bindgen_libclang_failure(self
):
262 bindgen
= self
.generate_bindgen_libclang_failure()
263 result
= self
.run_script(self
.Expected
.FAILURE
, { "BINDGEN": bindgen
})
264 self
.assertIn(f
"Running '{bindgen}' to check the libclang version (used by the Rust", result
.stderr
)
265 self
.assertIn(f
"bindings generator) failed with code {self.bindgen_default_bindgen_libclang_failure_exit_code}. This may be caused by", result
.stderr
)
267 def test_bindgen_libclang_unexpected_version(self
):
268 bindgen
= self
.generate_bindgen_libclang("scripts/rust_is_available_bindgen_libclang.h:2:9: warning: clang version unexpected [-W#pragma-messages], err: false")
269 result
= self
.run_script(self
.Expected
.FAILURE
, { "BINDGEN": bindgen
})
270 self
.assertIn(f
"Running '{bindgen}' to check the libclang version (used by the Rust", result
.stderr
)
271 self
.assertIn("bindings generator) did not return an expected output. See output", result
.stderr
)
273 def test_bindgen_libclang_old_version(self
):
274 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")
275 result
= self
.run_script(self
.Expected
.FAILURE
, { "BINDGEN": bindgen
})
276 self
.assertIn(f
"libclang (used by the Rust bindings generator '{bindgen}') is too old.", result
.stderr
)
278 def test_bindgen_bad_libclang_concat(self
):
279 for (bindgen_version
, libclang_version
, expected_not_patched
) in (
280 ("0.69.4", "18.0.0", self
.Expected
.SUCCESS
),
281 ("0.69.4", "19.1.0", self
.Expected
.SUCCESS_WITH_WARNINGS
),
282 ("0.69.4", "19.2.0", self
.Expected
.SUCCESS_WITH_WARNINGS
),
284 ("0.69.5", "18.0.0", self
.Expected
.SUCCESS
),
285 ("0.69.5", "19.1.0", self
.Expected
.SUCCESS
),
286 ("0.69.5", "19.2.0", self
.Expected
.SUCCESS
),
288 ("0.70.0", "18.0.0", self
.Expected
.SUCCESS
),
289 ("0.70.0", "19.1.0", self
.Expected
.SUCCESS
),
290 ("0.70.0", "19.2.0", self
.Expected
.SUCCESS
),
292 with self
.subTest(bindgen_version
=bindgen_version
, libclang_version
=libclang_version
):
293 cc
= self
.generate_clang(f
"clang version {libclang_version}")
294 libclang_stderr
= f
"scripts/rust_is_available_bindgen_libclang.h:2:9: warning: clang version {libclang_version} [-W#pragma-messages], err: false"
295 bindgen
= self
.generate_bindgen(f
"bindgen {bindgen_version}", libclang_stderr
)
296 result
= self
.run_script(expected_not_patched
, { "BINDGEN": bindgen
, "CC": cc
})
297 if expected_not_patched
== self
.Expected
.SUCCESS_WITH_WARNINGS
:
298 self
.assertIn(f
"Rust bindings generator '{bindgen}' < 0.69.5 together with libclang >= 19.1", result
.stderr
)
300 bindgen
= self
.generate_bindgen(f
"bindgen {bindgen_version}", libclang_stderr
, libclang_concat_patched
=True)
301 result
= self
.run_script(self
.Expected
.SUCCESS
, { "BINDGEN": bindgen
, "CC": cc
})
303 def test_clang_matches_bindgen_libclang_different_bindgen(self
):
304 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")
305 result
= self
.run_script(self
.Expected
.SUCCESS_WITH_WARNINGS
, { "BINDGEN": bindgen
})
306 self
.assertIn("version does not match Clang's. This may be a problem.", result
.stderr
)
308 def test_clang_matches_bindgen_libclang_different_clang(self
):
309 cc
= self
.generate_clang("clang version 999.0.0")
310 result
= self
.run_script(self
.Expected
.SUCCESS_WITH_WARNINGS
, { "CC": cc
})
311 self
.assertIn("version does not match Clang's. This may be a problem.", result
.stderr
)
313 def test_rustc_src_core_krustflags(self
):
314 result
= self
.run_script(self
.Expected
.FAILURE
, { "PATH": os
.environ
["PATH"], "RUSTC": "rustc", "KRUSTFLAGS": f
"--sysroot={self.missing}" })
315 self
.assertIn("Source code for the 'core' standard library could not be found", result
.stderr
)
317 def test_rustc_src_core_rustlibsrc(self
):
318 result
= self
.run_script(self
.Expected
.FAILURE
, { "RUST_LIB_SRC": self
.missing
})
319 self
.assertIn("Source code for the 'core' standard library could not be found", result
.stderr
)
321 def test_success_cc_unknown(self
):
322 result
= self
.run_script(self
.Expected
.SUCCESS_WITH_EXTRA_OUTPUT
, { "CC": self
.missing
})
323 self
.assertIn("unknown C compiler", result
.stderr
)
325 def test_success_cc_multiple_arguments_ccache(self
):
326 clang
= self
.generate_clang(f
"""Ubuntu clang version {self.llvm_default_version}-1ubuntu1
327 Target: x86_64-pc-linux-gnu
329 InstalledDir: /usr/bin
331 result
= self
.run_script(self
.Expected
.SUCCESS
, { "CC": f
"{clang} clang" })
333 def test_success_rustc_version(self
):
334 for rustc_stdout
in (
335 f
"rustc {self.rustc_default_version} (a8314ef7d 2022-06-27)",
336 f
"rustc {self.rustc_default_version}-dev (a8314ef7d 2022-06-27)",
337 f
"rustc {self.rustc_default_version}-1.60.0 (a8314ef7d 2022-06-27)",
339 with self
.subTest(rustc_stdout
=rustc_stdout
):
340 rustc
= self
.generate_rustc(rustc_stdout
)
341 result
= self
.run_script(self
.Expected
.SUCCESS
, { "RUSTC": rustc
})
343 def test_success_bindgen_version(self
):
344 for bindgen_stdout
in (
345 f
"bindgen {self.bindgen_default_version}",
346 f
"bindgen {self.bindgen_default_version}-dev",
347 f
"bindgen {self.bindgen_default_version}-0.999.0",
349 with self
.subTest(bindgen_stdout
=bindgen_stdout
):
350 bindgen
= self
.generate_bindgen_version(bindgen_stdout
)
351 result
= self
.run_script(self
.Expected
.SUCCESS
, { "BINDGEN": bindgen
})
353 def test_success_bindgen_libclang(self
):
355 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",
356 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",
357 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",
359 /nix/store/dsd5gz46hdbdk2rfdimqddhq6m8m8fqs-bash-5.1-p16/bin/bash: warning: setlocale: LC_ALL: cannot change locale (c)
360 scripts/rust_is_available_bindgen_libclang.h:2:9: warning: clang version {self.llvm_default_version} [-W#pragma-messages], err: false
363 /nix/store/dsd5gz46hdbdk2rfdimqddhq6m8m8fqs-bash-5.1.0-p16/bin/bash: warning: setlocale: LC_ALL: cannot change locale (c)
364 /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
367 with self
.subTest(stderr
=stderr
):
368 bindgen
= self
.generate_bindgen_libclang(stderr
)
369 result
= self
.run_script(self
.Expected
.SUCCESS
, { "BINDGEN": bindgen
})
371 def test_success_clang_version(self
):
372 for clang_stdout
in (
373 f
"clang version {self.llvm_default_version} (https://github.com/llvm/llvm-project.git 4a2c05b05ed07f1f620e94f6524a8b4b2760a0b1)",
374 f
"clang version {self.llvm_default_version}-dev",
375 f
"clang version {self.llvm_default_version}-2~ubuntu20.04.1",
376 f
"Ubuntu clang version {self.llvm_default_version}-2~ubuntu20.04.1",
378 with self
.subTest(clang_stdout
=clang_stdout
):
379 clang
= self
.generate_clang(clang_stdout
)
380 result
= self
.run_script(self
.Expected
.SUCCESS
, { "CC": clang
})
382 def test_success_real_programs(self
):
383 for cc
in ["gcc", "clang"]:
384 with self
.subTest(cc
=cc
):
385 result
= self
.run_script(self
.Expected
.SUCCESS
, {
386 "PATH": os
.environ
["PATH"],
388 "BINDGEN": "bindgen",
392 if __name__
== "__main__":