GeezerVison is showing github.com — Merge main after exception setter dependency landed · youknowdot/RustPython@3c4358a
File treeExpand file treeCollapse file tree Open diff view settings Some content is hiddenLarge Commits have some content hidden by default. Use the searchbox below for content that may be hidden. Dismiss bannerExpand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: Lib/test/test_atexit.py +57-1Lines changed: 57 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
| 3 | 3 | import subprocess | 4 | 4 | import textwrap | 5 | 5 | import unittest | 6 | | -from test.support import os_helper | 7 | 6 | from test import support | 8 | 7 | from test.support import SuppressCrashReport, script_helper | | 8 | +from test.support import os_helper | 9 | 9 | from test.support import threading_helper | 10 | 10 |
| 11 | 11 | class GeneralTest(unittest.TestCase): | @@ -82,6 +82,62 @@ def thready(): | 82 | 82 | # want them to affect the rest of the tests. | 83 | 83 | script_helper.assert_python_ok("-c", textwrap.dedent(source)) | 84 | 84 |
| | 85 | + @threading_helper.requires_working_threading() | | 86 | + def test_thread_created_in_atexit(self): | | 87 | + source = """if True: | | 88 | + import atexit | | 89 | + import threading | | 90 | + import time | | 91 | +
| | 92 | +
| | 93 | + def run(): | | 94 | + print(24) | | 95 | + time.sleep(1) | | 96 | + print(42) | | 97 | +
| | 98 | + @atexit.register | | 99 | + def start_thread(): | | 100 | + threading.Thread(target=run).start() | | 101 | + """ | | 102 | + return_code, stdout, stderr = script_helper.assert_python_ok("-c", source) | | 103 | + self.assertEqual(return_code, 0) | | 104 | + self.assertEqual(stdout, f"24{os.linesep}42{os.linesep}".encode("utf-8")) | | 105 | + self.assertEqual(stderr, b"") | | 106 | + | | 107 | + @threading_helper.requires_working_threading() | | 108 | + @unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()") | | 109 | + def test_thread_created_in_atexit_subinterpreter(self): | | 110 | + try: | | 111 | + from concurrent import interpreters | | 112 | + except ImportError: | | 113 | + self.skipTest("subinterpreters are not available") | | 114 | + | | 115 | + read, write = os.pipe() | | 116 | + source = f"""if True: | | 117 | + import atexit | | 118 | + import threading | | 119 | + import time | | 120 | + import os | | 121 | +
| | 122 | + def run(): | | 123 | + os.write({write}, b'spanish') | | 124 | + time.sleep(1) | | 125 | + os.write({write}, b'inquisition') | | 126 | +
| | 127 | + @atexit.register | | 128 | + def start_thread(): | | 129 | + threading.Thread(target=run).start() | | 130 | + """ | | 131 | + interp = interpreters.create() | | 132 | + try: | | 133 | + interp.exec(source) | | 134 | + | | 135 | + # Close the interpreter to invoke atexit callbacks | | 136 | + interp.close() | | 137 | + self.assertEqual(os.read(read, 100), b"spanishinquisition") | | 138 | + finally: | | 139 | + os.close(read) | | 140 | + os.close(write) | 85 | 141 |
| 86 | 142 | @support.cpython_only | 87 | 143 | class SubinterpreterTest(unittest.TestCase): | |
Collapse file Copy file name to clipboardExpand all lines: Lib/test/test_frame.py -1Lines changed: 0 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -776,7 +776,6 @@ def run(self): | 776 | 776 | ) | 777 | 777 | sneaky_frame_object = sneaky_frame_object.f_back | 778 | 778 |
| 779 | | - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'NoneType' object has no attribute 'exc_type' | 780 | 779 | def test_entry_frames_are_invisible_during_teardown(self): | 781 | 780 | class C: | 782 | 781 | """A weakref'able class.""" | |
Collapse file Copy file name to clipboardExpand all lines: Lib/test/test_long.py -1Lines changed: 0 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -403,7 +403,6 @@ def check_float_conversion(self, n): | 403 | 403 | "Got {}, expected {}.".format(n, actual, expected)) | 404 | 404 | self.assertEqual(actual, expected, msg) | 405 | 405 |
| 406 | | - @unittest.expectedFailure # TODO: RUSTPYTHON | 407 | 406 | @support.requires_IEEE_754 | 408 | 407 | def test_float_conversion(self): | 409 | 408 |
| |
Collapse file Copy file name to clipboardExpand all lines: Lib/test/test_os/test_os.py -1Lines changed: 0 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -2727,7 +2727,6 @@ def test_execve_invalid_env(self): | 2727 | 2727 |
| 2728 | 2728 | # See https://github.com/python/cpython/issues/137934 and the other | 2729 | 2729 | # related issues for the reason why we cannot test this on Windows. | 2730 | | - @unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: MyEnv.__getitem__() takes 1 positional argument but 2 were given | 2731 | 2730 | @unittest.skipIf(os.name == "nt", "POSIX-specific test") | 2732 | 2731 | @unittest.skipUnless(unix_shell and os.path.exists(unix_shell), | 2733 | 2732 | "requires a shell") | |
Collapse file Copy file name to clipboardExpand all lines: Lib/test/test_pyexpat.py -1Lines changed: 0 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -407,7 +407,6 @@ def test_legal(self): | 407 | 407 | expat.ParserCreate(namespace_separator=None) | 408 | 408 | expat.ParserCreate(namespace_separator=' ') | 409 | 409 |
| 410 | | - @unittest.expectedFailure # TODO: RUSTPYTHON | 411 | 410 | def test_illegal(self): | 412 | 411 | with self.assertRaisesRegex(TypeError, | 413 | 412 | r"ParserCreate\(\) argument (2|'namespace_separator') " | |
Collapse file Copy file name to clipboardExpand all lines: Lib/test/test_regrtest.py -1Lines changed: 0 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -1902,7 +1902,6 @@ def test_sleep(self): | 1902 | 1902 | self.assertRegex(output, | 1903 | 1903 | re.compile('%s timed out' % testname, re.MULTILINE)) | 1904 | 1904 |
| 1905 | | - @unittest.expectedFailure # TODO: RUSTPYTHON; test_unraisable_exc (test_regrtest_noop39.Tests.test_unraisable_exc) ... ok | 1906 | 1905 | def test_unraisable_exc(self): | 1907 | 1906 | # --fail-env-changed must catch unraisable exception. | 1908 | 1907 | # The exception must be displayed even if sys.stderr is redirected. | |
Collapse file Copy file name to clipboardExpand all lines: Lib/test/test_socket.py -5Lines changed: 0 additions & 5 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -7160,7 +7160,6 @@ def create_alg(self, typ, name): | 7160 | 7160 |
| 7161 | 7161 | # bpo-31705: On kernel older than 4.5, sendto() failed with ENOKEY, | 7162 | 7162 | # at least on ppc64le architecture | 7163 | | - @unittest.expectedFailure # TODO: RUSTPYTHON; - AF_ALG not fully implemented | 7164 | 7163 | @support.requires_linux_version(4, 5) | 7165 | 7164 | def test_sha256(self): | 7166 | 7165 | expected = bytes.fromhex("ba7816bf8f01cfea414140de5dae2223b00361a396" | @@ -7179,7 +7178,6 @@ def test_sha256(self): | 7179 | 7178 | op.send(b'') | 7180 | 7179 | self.assertEqual(op.recv(512), expected) | 7181 | 7180 |
| 7182 | | - @unittest.expectedFailure # TODO: RUSTPYTHON; - AF_ALG not fully implemented | 7183 | 7181 | def test_hmac_sha1(self): | 7184 | 7182 | # gh-109396: In FIPS mode, Linux 6.5 requires a key | 7185 | 7183 | # of at least 112 bits. Use a key of 152 bits. | @@ -7195,7 +7193,6 @@ def test_hmac_sha1(self): | 7195 | 7193 |
| 7196 | 7194 | # Although it should work with 3.19 and newer the test blocks on | 7197 | 7195 | # Ubuntu 15.10 with Kernel 4.2.0-19. | 7198 | | - @unittest.expectedFailure # TODO: RUSTPYTHON; - AF_ALG not fully implemented | 7199 | 7196 | @support.requires_linux_version(4, 3) | 7200 | 7197 | def test_aes_cbc(self): | 7201 | 7198 | key = bytes.fromhex('06a9214036b8a15b512e03d534120006') | @@ -7237,7 +7234,6 @@ def test_aes_cbc(self): | 7237 | 7234 | self.assertEqual(len(dec), msglen * multiplier) | 7238 | 7235 | self.assertEqual(dec, msg * multiplier) | 7239 | 7236 |
| 7240 | | - @unittest.expectedFailure # TODO: RUSTPYTHON; - AF_ALG not fully implemented | 7241 | 7237 | @support.requires_linux_version(4, 9) # see gh-73510 | 7242 | 7238 | def test_aead_aes_gcm(self): | 7243 | 7239 | kernel_version = support._get_kernel_version("Linux") | @@ -7307,7 +7303,6 @@ def test_aead_aes_gcm(self): | 7307 | 7303 | res = op.recv(len(msg) - taglen) | 7308 | 7304 | self.assertEqual(plain, res[assoclen:]) | 7309 | 7305 |
| 7310 | | - @unittest.expectedFailure # TODO: RUSTPYTHON; - AF_ALG not fully implemented | 7311 | 7306 | @support.requires_linux_version(4, 3) # see test_aes_cbc | 7312 | 7307 | def test_drbg_pr_sha256(self): | 7313 | 7308 | # deterministic random bit generator, prediction resistance, sha256 | |
Collapse file Copy file name to clipboardExpand all lines: crates/common/src/int.rs +11-9Lines changed: 11 additions & 9 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -6,13 +6,10 @@ use num_traits::{One, ToPrimitive, Zero}; | 6 | 6 | #[must_use] | 7 | 7 | pub fn true_div(numerator: &BigInt, denominator: &BigInt) -> f64 { | 8 | 8 | let rational = Rational::from_integers_ref(numerator.into(), denominator.into()); | 9 | | - match rational.rounding_into(RoundingMode::Nearest) { | 10 | | - // returned value is $t::MAX but still less than the original | 11 | | - (val, core::cmp::Ordering::Less) if val == f64::MAX => f64::INFINITY, | 12 | | - // returned value is $t::MIN but still greater than the original | 13 | | - (val, core::cmp::Ordering::Greater) if val == f64::MIN => f64::NEG_INFINITY, | 14 | | - (val, _) => val, | 15 | | - } | | 9 | + // Nearest rounding can return the largest finite value for a quotient | | 10 | + // slightly beyond it; overflow starts at the halfway rounding boundary. | | 11 | + let (rounded, _) = rational.rounding_into(RoundingMode::Nearest); | | 12 | + rounded | 16 | 13 | } | 17 | 14 |
| 18 | 15 | #[must_use] | @@ -202,11 +199,16 @@ fn word_all_ascii_digits(word: u64) -> bool { | 202 | 199 | (below | above) & 0x8080_8080_8080_8080 == 0 | 203 | 200 | } | 204 | 201 |
| 205 | | -// num-bigint now returns Some(inf) for to_f64() in some cases, so just keep that the same for now | | 202 | +/// Convert an integer to a finite float, or return None when it overflows. | 206 | 203 | #[inline(always)] | 207 | 204 | #[must_use] | 208 | 205 | pub fn bigint_to_finite_float(int: &BigInt) -> Option<f64> { | 209 | | - int.to_f64().filter(|f| f.is_finite()) | | 206 | + // malachite-bigint's ToPrimitive returns None when the input exceeds | | 207 | + // the largest finite magnitude, even when nearest rounding is finite. | | 208 | + let value = int | | 209 | + .to_f64() | | 210 | + .unwrap_or_else(|| true_div(int, &BigInt::one())); | | 211 | + value.is_finite().then_some(value) | 210 | 212 | } | 211 | 213 |
| 212 | 214 | #[cfg(test)] | |
Collapse file Copy file name to clipboardExpand all lines: crates/derive-impl/src/from_args.rs +70Lines changed: 70 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -657,3 +657,73 @@ pub(crate) fn impl_from_args(input: DeriveInput) -> Result<TokenStream> { | 657 | 657 | }; | 658 | 658 | Ok(output) | 659 | 659 | } | | 660 | + | | 661 | +#[cfg(test)] | | 662 | +mod tests { | | 663 | + use super::*; | | 664 | + | | 665 | + #[test] | | 666 | + fn option_none_default_keeps_rust_value_and_metadata() { | | 667 | + let types: [Type; 2] = [ | | 668 | + parse_quote!(Option<PyObjectRef>), | | 669 | + parse_quote!(Option<PyStrRef>), | | 670 | + ]; | | 671 | + for ty in types { | | 672 | + let field: Field = parse_quote! { | | 673 | + #[pyarg(any, default = None)] | | 674 | + value: #ty | | 675 | + }; | | 676 | + let tokens = generate_field((0, &field)).unwrap().to_string(); | | 677 | + assert!(tokens.contains("e!(unwrap_or_else(|| None)).to_string())); | | 678 | + assert!(!tokens.contains("vm . ctx . none")); | | 679 | + let attr = ArgAttribute::try_from(&field).unwrap(); | | 680 | + let repr = repr_path(); | | 681 | + assert_eq!( | | 682 | + signature_default(&field, &attr).unwrap().to_string(), | | 683 | + quote!(Some(#repr::None)).to_string() | | 684 | + ); | | 685 | + } | | 686 | + } | | 687 | + | | 688 | + #[test] | | 689 | + fn none_default_keeps_wrapped_argument_paths() { | | 690 | + let types: [Type; 3] = [ | | 691 | + parse_quote!(Option<PyObjectRef>), | | 692 | + parse_quote!(OptionalArg<PyObjectRef>), | | 693 | + parse_quote!(OptionalOption<PyObjectRef>), | | 694 | + ]; | | 695 | + for ty in types { | | 696 | + let field: Field = parse_quote! { | | 697 | + #[pyarg(any, default = None)] | | 698 | + value: #ty | | 699 | + }; | | 700 | + let tokens = generate_field((0, &field)).unwrap().to_string(); | | 701 | + assert!(!tokens.contains("vm . ctx . none")); | | 702 | + assert!(tokens.contains("e!(unwrap_or_else(|| None)).to_string())); | | 703 | + } | | 704 | + } | | 705 | + | | 706 | + #[test] | | 707 | + fn none_is_not_a_python_object_literal() { | | 708 | + for expr in [parse_quote!(None), parse_quote!((None))] { | | 709 | + assert!(literal_object_expr(&expr).is_none()); | | 710 | + } | | 711 | + for expr in [ | | 712 | + parse_quote!("text"), | | 713 | + parse_quote!(b"bytes"), | | 714 | + parse_quote!(true), | | 715 | + ] { | | 716 | + assert_eq!( | | 717 | + literal_object_expr(&expr).unwrap().to_string(), | | 718 | + quote!(#expr).to_string() | | 719 | + ); | | 720 | + } | | 721 | + let field: Field = parse_quote! { | | 722 | + #[pyarg(any, default = "text")] | | 723 | + value: PyStrRef | | 724 | + }; | | 725 | + let tokens = generate_field((0, &field)).unwrap().to_string(); | | 726 | + assert!(tokens.contains("< PyStrRef as :: rustpython_vm :: convert :: TryFromObject >")); | | 727 | + assert!(!tokens.contains("vm . ctx . none")); | | 728 | + } | | 729 | +} |
Collapse file Copy file name to clipboardExpand all lines: crates/derive/src/lib.rs +4Lines changed: 4 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -26,6 +26,10 @@ use syn::punctuated::Punctuated; | 26 | 26 | /// `OptionalArg<T>` is converted only when the argument is missing: | 27 | 27 | /// `<FieldTy as TryFromObject>::try_from_object(vm, ToPyObject::to_pyobject(LIT, vm))?`. | 28 | 28 | /// A byte-string literal becomes a Python `bytes` object. | | 29 | +/// `default = None` on `Option<T>`, including `Option<PyObjectRef>`, stores | | 30 | +/// Rust `None` and displays `None` in the signature without `py_default`. | | 31 | +/// It does not construct Python's None object for an unwrapped field or | | 32 | +/// mean `OptionalArg::Missing`. | 29 | 33 | /// - `default = ::NAME`: `NAME` is one identifier. The signature copies that | 30 | 34 | /// name, and the missing argument stores `Into::into(NAME)` (the leading | 31 | 35 | /// `::` is not Rust syntax for a local constant). A longer `::` path is a | |
|