From 72f11c8bfaedb4260fe9d29b2f9bd26d83b8c18a Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Wed, 28 Jan 2026 15:37:14 -0500 Subject: [PATCH 01/11] Attempt to fix failing monitor test (#15992) --- test/monitor/test_monitor.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/monitor/test_monitor.rb b/test/monitor/test_monitor.rb index 4c55afca6c869e..7a26831bafe2ba 100644 --- a/test/monitor/test_monitor.rb +++ b/test/monitor/test_monitor.rb @@ -274,7 +274,7 @@ def test_timedwait @monitor.synchronize do queue2.enq(nil) assert_equal("foo", b) - result2 = cond.wait(0.1) + result2 = cond.wait(10) assert_equal(true, result2) assert_equal("bar", b) end From 2d5460e4d78032584dbf261259a8b05b856dffdf Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Wed, 28 Jan 2026 13:32:33 -0800 Subject: [PATCH 02/11] ZJIT: Optimize send-with-block to iseq methods (#15911) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ~~Two commits:~~ ### zjit: Optimize send-with-block to iseq methods This commit enables JIT-to-JIT calls for send instructions with literal blocks (e.g., `foo { |x| x * 2 }`), rather than falling back to the `rb_vm_send` C wrapper. This optimization applies to both methods with explicit block parameters (`def foo(&block)`) and methods implicitly accepting a block (`def foo; yield if block_given?; end`). Prior to this change, any callsite with a block would miss out on the JIT-to-JIT fast path and goes through a `rb_vm_send` C wrapper call. Initially, as https://github.com/Shopify/ruby/issues/931 suggested, we assumed this would involve changes to the JIT-to-JIT calling convention to accommodate passing a block argument. However, during implementation, I discovered that @nirvdrum had already wired up the `specval` protocol used by the interpreter in their `invokesuper` work (https://github.com/ruby/ruby/pull/887). That infrastructure remained dormant but was exactly what we needed here. After plumbing everything through, it Just Worked™. It may be possible to design a more direct JIT-to-JIT protocol for passing blocks. In the HIR for `def foo(&block)`, the BB for the JIT entrypoint already takes two arguments (self + &block, presumably), and since `yield` is a keyword, it may be possible to rewrite the implicit case to be explicit (thanks @tenderlove for the idea), and do "better" than passing via `specval`. I'm not sure if that's a goal eventually, but in any case, if `specval` works, there is no harm in enabling this optimization today. Implementation notes: This initial pass largely duplicates the existing `SendWithoutBlock` to `SendWithoutBlockDirect` specialization logic. A future refactor could potentially collapse Send and SendWithoutBlock into a single instruction variant (with `blockiseq: Option`, you can always pattern match the Option if needed), since they now follow very similar paths. However, I wanted to keep this PR focused and also get feedback on that direction first before committing to such a big refactor. The optimization currently handles `VM_METHOD_TYPE_ISEQ` only. It does not handle "block to block" `VM_METHOD_TYPE_BMETHOD`. It's unclear if that'd be all that difficult, I just didn't try. Happy to do it as a follow-up. Any callsites not handled by this specialization continue to fallthrough to the existing rb_vm_send harness safely. Test coverage includes both explicit block parameters and yield-based methods. Thanks to @tenderlove for initial ideas and walkthrough, and @nirvdrum for the foundation this builds on. Closes https://github.com/Shopify/ruby/issues/931 ### ~~zjit: Allow SendWithoutBlockDirect to def f(&blk)~~ Saving this for another time ### Follow-ups * [ ] Refactor and simplify by merging `hir::Insn::Send` and `hir::Insn::SendWithoutBlock`. (Pending feedback/approval.) * [ ] Handle block-to-block `VM_METHOD_TYPE_BMETHOD` calls. It appears that `gen_send_iseq_direct` already attempted to handle it. * [ ] As far as I can tell, we should be able to just enable `super { ... }` too, happy to do that as a follow-up if @nirvdrum doesn't have time for it. --- zjit/src/codegen.rs | 48 +++++++--- zjit/src/hir.rs | 188 +++++++++++++++++++++++++++----------- zjit/src/hir/opt_tests.rs | 122 ++++++++++++++----------- zjit/src/hir/tests.rs | 8 +- zjit/src/stats.rs | 8 +- 5 files changed, 248 insertions(+), 126 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index a77bd7debd0dfc..5d6060dd49d3a3 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -478,8 +478,8 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::Snapshot { .. } => return Ok(()), // we don't need to do anything for this instruction at the moment &Insn::Send { cd, blockiseq, state, reason, .. } => gen_send(jit, asm, cd, blockiseq, &function.frame_state(state), reason), &Insn::SendForward { cd, blockiseq, state, reason, .. } => gen_send_forward(jit, asm, cd, blockiseq, &function.frame_state(state), reason), + Insn::SendDirect { cme, iseq, recv, args, kw_bits, blockiseq, state, .. } => gen_send_iseq_direct(cb, jit, asm, *cme, *iseq, opnd!(recv), opnds!(args), *kw_bits, &function.frame_state(*state), *blockiseq), &Insn::SendWithoutBlock { cd, state, reason, .. } => gen_send_without_block(jit, asm, cd, &function.frame_state(state), reason), - Insn::SendWithoutBlockDirect { cme, iseq, recv, args, kw_bits, state, .. } => gen_send_iseq_direct(cb, jit, asm, *cme, *iseq, opnd!(recv), opnds!(args), *kw_bits, &function.frame_state(*state), None), &Insn::InvokeSuper { cd, blockiseq, state, reason, .. } => gen_invokesuper(jit, asm, cd, blockiseq, &function.frame_state(state), reason), &Insn::InvokeSuperForward { cd, blockiseq, state, reason, .. } => gen_invokesuperforward(jit, asm, cd, blockiseq, &function.frame_state(state), reason), &Insn::InvokeBlock { cd, state, reason, .. } => gen_invokeblock(jit, asm, cd, &function.frame_state(state), reason), @@ -1024,6 +1024,15 @@ fn gen_ccall(asm: &mut Assembler, cfunc: *const u8, name: ID, recv: Opnd, args: asm.ccall(cfunc, cfunc_args) } +// Change cfp->block_code in the current frame. See vm_caller_setup_arg_block(). +// VM_CFP_TO_CAPTURED_BLOCK then turns &cfp->self into a block handler. +// rb_captured_block->code.iseq aliases with cfp->block_code. +fn gen_block_handler_specval(asm: &mut Assembler, blockiseq: IseqPtr) -> lir::Opnd { + asm.store(Opnd::mem(VALUE_BITS, CFP, RUBY_OFFSET_CFP_BLOCK_CODE), VALUE::from(blockiseq).into()); + let cfp_self_addr = asm.lea(Opnd::mem(VALUE_BITS, CFP, RUBY_OFFSET_CFP_SELF)); + asm.or(cfp_self_addr, Opnd::Imm(1)) +} + /// Generate code for a variadic C function call /// func(int argc, VALUE *argv, VALUE recv) fn gen_ccall_variadic( @@ -1053,13 +1062,8 @@ fn gen_ccall_variadic( gen_spill_stack(jit, asm, state); gen_spill_locals(jit, asm, state); - let block_handler_specval = if let Some(block_iseq) = blockiseq { - // Change cfp->block_code in the current frame. See vm_caller_setup_arg_block(). - // VM_CFP_TO_CAPTURED_BLOCK then turns &cfp->self into a block handler. - // rb_captured_block->code.iseq aliases with cfp->block_code. - asm.store(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_BLOCK_CODE), VALUE::from(block_iseq).into()); - let cfp_self_addr = asm.lea(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_SELF)); - asm.or(cfp_self_addr, Opnd::Imm(1)) + let block_handler_specval = if let Some(blockiseq) = blockiseq { + gen_block_handler_specval(asm, blockiseq) } else { VM_BLOCK_HANDLER_NONE.into() }; @@ -1446,7 +1450,7 @@ fn gen_send_iseq_direct( args: Vec, kw_bits: u32, state: &FrameState, - block_handler: Option, + blockiseq: Option, ) -> lir::Opnd { gen_incr_counter(asm, Counter::iseq_optimized_send_count); @@ -1462,6 +1466,12 @@ fn gen_send_iseq_direct( gen_spill_locals(jit, asm, state); gen_spill_stack(jit, asm, state); + // This mirrors vm_caller_setup_arg_block() in for the `blockiseq != NULL` case. + // The HIR specialization guards ensure we will only reach here for literal blocks, + // not &block forwarding, &:foo, etc. Thise are rejected in `type_specialize` by + // `unspecializable_call_type`. + let block_handler = blockiseq.map(|b| gen_block_handler_specval(asm, b)); + let (frame_type, specval) = if VM_METHOD_TYPE_BMETHOD == unsafe { get_cme_def_type(cme) } { // Extract EP from the Proc instance let procv = unsafe { rb_get_def_bmethod_proc((*cme).def) }; @@ -1513,11 +1523,25 @@ fn gen_send_iseq_direct( asm.mov(CFP, new_cfp); asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP as i32), CFP); + let params = unsafe { iseq.params() }; + + // For &block, the JIT entrypoint expects the block_handler as an argument + // This HIR param is not actually used, things read from specval from the VM frame today. + // TODO: Remove unused param from HIR, or pass specval through c_args. + // See https://github.com/ruby/ruby/pull/15911#discussion_r2710544982 + let needs_block = params.flags.has_block() != 0; + // Set up arguments - let mut c_args = vec![recv]; + let mut c_args = Vec::with_capacity({ + // This is a heuristic to avoid re-allocation, not necessary for correctness + 1 /* recv */ + args.len() + if needs_block { 1 } else { 0 } + }); + c_args.push(recv); c_args.extend(&args); + if needs_block { + c_args.push(specval); + } - let params = unsafe { iseq.params() }; let num_optionals_passed = if params.flags.has_opt() != 0 { // See vm_call_iseq_setup_normal_opt_start in vm_inshelper.c let lead_num = params.lead_num as u32; @@ -2714,7 +2738,7 @@ fn function_stub_hit_body(cb: &mut CodeBlock, iseq_call: &IseqCallRef) -> Result Ok(jit_entry_ptr) } -/// Compile a stub for an ISEQ called by SendWithoutBlockDirect +/// Compile a stub for an ISEQ called by SendDirect fn gen_function_stub(cb: &mut CodeBlock, iseq_call: IseqCallRef) -> Result { let (mut asm, scratch_reg) = Assembler::new_with_scratch_reg(); asm.new_block_without_id(); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 51ab45937cb5d2..b523d8430f3e5e 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -625,10 +625,10 @@ pub enum SendFallbackReason { SendWithoutBlockNotOptimizedNeedPermission, SendWithoutBlockBopRedefined, SendWithoutBlockOperandsNotFixnum, - SendWithoutBlockDirectKeywordMismatch, - SendWithoutBlockDirectKeywordCountMismatch, - SendWithoutBlockDirectMissingKeyword, - SendWithoutBlockDirectTooManyKeywords, + SendDirectKeywordMismatch, + SendDirectKeywordCountMismatch, + SendDirectMissingKeyword, + SendDirectTooManyKeywords, SendPolymorphic, SendMegamorphic, SendNoProfiles, @@ -686,10 +686,10 @@ impl Display for SendFallbackReason { SendNotOptimizedNeedPermission => write!(f, "Send: method private or protected and no FCALL"), SendWithoutBlockBopRedefined => write!(f, "SendWithoutBlock: basic operation was redefined"), SendWithoutBlockOperandsNotFixnum => write!(f, "SendWithoutBlock: operands are not fixnums"), - SendWithoutBlockDirectKeywordMismatch => write!(f, "SendWithoutBlockDirect: keyword mismatch"), - SendWithoutBlockDirectKeywordCountMismatch => write!(f, "SendWithoutBlockDirect: keyword count mismatch"), - SendWithoutBlockDirectMissingKeyword => write!(f, "SendWithoutBlockDirect: missing keyword"), - SendWithoutBlockDirectTooManyKeywords => write!(f, "SendWithoutBlockDirect: too many keywords for fixnum bitmask"), + SendDirectKeywordMismatch => write!(f, "SendDirect: keyword mismatch"), + SendDirectKeywordCountMismatch => write!(f, "SendDirect: keyword count mismatch"), + SendDirectMissingKeyword => write!(f, "SendDirect: missing keyword"), + SendDirectTooManyKeywords => write!(f, "SendDirect: too many keywords for fixnum bitmask"), SendPolymorphic => write!(f, "Send: polymorphic call site"), SendMegamorphic => write!(f, "Send: megamorphic call site"), SendNoProfiles => write!(f, "Send: no profile data available"), @@ -959,13 +959,14 @@ pub enum Insn { }, /// Optimized ISEQ call - SendWithoutBlockDirect { + SendDirect { recv: InsnId, cd: *const rb_call_data, cme: *const rb_callable_method_entry_t, iseq: IseqPtr, args: Vec, kw_bits: u32, + blockiseq: Option, state: InsnId, }, @@ -1193,7 +1194,7 @@ impl Insn { Insn::InvokeSuper { .. } => effects::Any, Insn::InvokeSuperForward { .. } => effects::Any, Insn::InvokeBlock { .. } => effects::Any, - Insn::SendWithoutBlockDirect { .. } => effects::Any, + Insn::SendDirect { .. } => effects::Any, Insn::InvokeBuiltin { .. } => effects::Any, Insn::EntryPoint { .. } => effects::Any, Insn::Return { .. } => effects::Any, @@ -1446,8 +1447,8 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { write!(f, " # SendFallbackReason: {reason}")?; Ok(()) } - Insn::SendWithoutBlockDirect { recv, cd, iseq, args, .. } => { - write!(f, "SendWithoutBlockDirect {recv}, :{} ({:?})", ruby_call_method_name(*cd), self.ptr_map.map_ptr(iseq))?; + Insn::SendDirect { recv, cd, iseq, args, blockiseq, .. } => { + write!(f, "SendDirect {recv}, {:p}, :{} ({:?})", self.ptr_map.map_ptr(blockiseq), ruby_call_method_name(*cd), self.ptr_map.map_ptr(iseq))?; for arg in args { write!(f, ", {arg}")?; } @@ -1838,7 +1839,8 @@ pub enum ValidationError { MiscValidationError(InsnId, String), } -fn can_direct_send(function: &mut Function, block: BlockId, iseq: *const rb_iseq_t, ci: *const rb_callinfo, send_insn: InsnId, args: &[InsnId]) -> bool { +/// Check if we can do a direct send to the given iseq with the given args. +fn can_direct_send(function: &mut Function, block: BlockId, iseq: *const rb_iseq_t, ci: *const rb_callinfo, send_insn: InsnId, args: &[InsnId], blockiseq: Option) -> bool { let mut can_send = true; let mut count_failure = |counter| { can_send = false; @@ -1846,10 +1848,14 @@ fn can_direct_send(function: &mut Function, block: BlockId, iseq: *const rb_iseq }; let params = unsafe { iseq.params() }; + let caller_has_literal_block: bool = blockiseq.is_some(); + let callee_has_block_param = 0 != params.flags.has_block(); + use Counter::*; if 0 != params.flags.has_rest() { count_failure(complex_arg_pass_param_rest) } if 0 != params.flags.has_post() { count_failure(complex_arg_pass_param_post) } - if 0 != params.flags.has_block() { count_failure(complex_arg_pass_param_block) } + if callee_has_block_param && !caller_has_literal_block + { count_failure(complex_arg_pass_param_block) } if 0 != params.flags.forwardable() { count_failure(complex_arg_pass_param_forwardable) } if 0 != params.flags.has_kwrest() { count_failure(complex_arg_pass_param_kwrest) } @@ -1884,7 +1890,11 @@ fn can_direct_send(function: &mut Function, block: BlockId, iseq: *const rb_iseq let kwarg = unsafe { rb_vm_ci_kwarg(ci) }; let caller_kw_count = if kwarg.is_null() { 0 } else { (unsafe { get_cikw_keyword_len(kwarg) }) as usize }; let caller_positional = args.len() - caller_kw_count; - let final_argc = caller_positional + kw_total_num as usize; + // Right now, the JIT entrypoint accepts the block as an param + // We may remove it, remove the block_arg addition to match + // See: https://github.com/ruby/ruby/pull/15911#discussion_r2710544982 + let block_arg = if 0 != params.flags.has_block() { 1 } else { 0 }; + let final_argc = caller_positional + kw_total_num as usize + block_arg; if final_argc + 1 > C_ARG_OPNDS.len() { // +1 for self function.set_dynamic_send_reason(send_insn, TooManyArgsForLir); return false; @@ -2261,13 +2271,14 @@ impl Function { state, reason, }, - &SendWithoutBlockDirect { recv, cd, cme, iseq, ref args, kw_bits, state } => SendWithoutBlockDirect { + &SendDirect { recv, cd, cme, iseq, ref args, kw_bits, blockiseq, state } => SendDirect { recv: find!(recv), cd, cme, iseq, args: find_vec!(args), kw_bits, + blockiseq, state, }, &Send { recv, cd, blockiseq, ref args, state, reason } => Send { @@ -2499,7 +2510,7 @@ impl Function { Insn::FixnumRShift { .. } => types::Fixnum, Insn::PutSpecialObject { .. } => types::BasicObject, Insn::SendWithoutBlock { .. } => types::BasicObject, - Insn::SendWithoutBlockDirect { .. } => types::BasicObject, + Insn::SendDirect { .. } => types::BasicObject, Insn::Send { .. } => types::BasicObject, Insn::SendForward { .. } => types::BasicObject, Insn::InvokeSuper { .. } => types::BasicObject, @@ -2666,7 +2677,7 @@ impl Function { } /// Prepare arguments for a direct send, handling keyword argument reordering and default synthesis. - /// Returns the (state, processed_args, kw_bits) to use for the SendWithoutBlockDirect instruction, + /// Returns the (state, processed_args, kw_bits) to use for the SendDirect instruction, /// or Err with the fallback reason if direct send isn't possible. fn prepare_direct_send_args( &mut self, @@ -2711,7 +2722,7 @@ impl Function { if callee_keyword.is_null() { if !kwarg.is_null() { // Caller is passing kwargs but callee doesn't expect them. - return Err(SendWithoutBlockDirectKeywordMismatch); + return Err(SendDirectKeywordMismatch); } // Neither caller nor callee have keywords - nothing to do return Ok((args.to_vec(), args.len(), 0)); @@ -2724,7 +2735,7 @@ impl Function { // When there are 31+ keywords, CRuby uses a hash instead of a fixnum bitmask // for kw_bits. Fall back to VM dispatch for this rare case. if callee_kw_count >= VM_KW_SPECIFIED_BITS_MAX as usize { - return Err(SendWithoutBlockDirectTooManyKeywords); + return Err(SendDirectTooManyKeywords); } let callee_kw_required = unsafe { (*callee_keyword).required_num } as usize; @@ -2733,7 +2744,7 @@ impl Function { // Caller can't provide more keywords than callee expects (no **kwrest support yet). if caller_kw_count > callee_kw_count { - return Err(SendWithoutBlockDirectKeywordCountMismatch); + return Err(SendDirectKeywordCountMismatch); } // The keyword arguments are the last arguments in the args vector. @@ -2763,7 +2774,7 @@ impl Function { if !found { // Caller is passing an unknown keyword - this will raise ArgumentError. // Fall back to VM dispatch to handle the error. - return Err(SendWithoutBlockDirectKeywordMismatch); + return Err(SendDirectKeywordMismatch); } } @@ -2787,7 +2798,7 @@ impl Function { if !found { // Required keyword not provided by caller which will raise an ArgumentError. if i < callee_kw_required { - return Err(SendWithoutBlockDirectMissingKeyword); + return Err(SendDirectMissingKeyword); } // Optional keyword not provided - use default value @@ -2965,9 +2976,11 @@ impl Function { } } - /// Rewrite SendWithoutBlock opcodes into SendWithoutBlockDirect opcodes if we know the target - /// ISEQ statically. This removes run-time method lookups and opens the door for inlining. + /// Rewrite eligible Send/SendWithoutBlock opcodes into SendDirect + /// opcodes if we know the target ISEQ statically. This removes run-time method lookups and + /// opens the door for inlining. /// Also try and inline constant caches, specialize object allocations, and more. + /// Calls to C functions are handled separately in optimize_c_calls. fn type_specialize(&mut self) { for block in self.rpo() { let old_insns = std::mem::take(&mut self.blocks[block.0].insns); @@ -3037,7 +3050,7 @@ impl Function { def_type = unsafe { get_cme_def_type(cme) }; } - // If the call site info indicates that the `Function` has overly complex arguments, then do not optimize into a `SendWithoutBlockDirect`. + // If the call site info indicates that the `Function` has overly complex arguments, then do not optimize into a `SendDirect`. // Optimized methods(`VM_METHOD_TYPE_OPTIMIZED`) handle their own argument constraints (e.g., kw_splat for Proc call). if def_type != VM_METHOD_TYPE_OPTIMIZED && unspecializable_call_type(flags) { self.count_complex_call_features(block, flags); @@ -3050,15 +3063,20 @@ impl Function { // Only specialize positional-positional calls // TODO(max): Handle other kinds of parameter passing let iseq = unsafe { get_def_iseq_ptr((*cme).def) }; - if !can_direct_send(self, block, iseq, ci, insn_id, args.as_slice()) { + if !can_direct_send(self, block, iseq, ci, insn_id, args.as_slice(), None) { self.push_insn_id(block, insn_id); continue; } + // Check singleton class assumption first, before emitting other patchpoints if !self.assume_no_singleton_classes(block, klass, state) { self.set_dynamic_send_reason(insn_id, SingletonClassSeen); self.push_insn_id(block, insn_id); continue; } + + // Add PatchPoint for method redefinition self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state }); + + // Add GuardType for profiled receiver if let Some(profiled_type) = profiled_type { recv = self.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state }); } @@ -3068,7 +3086,7 @@ impl Function { self.push_insn_id(block, insn_id); continue; }; - let send_direct = self.push_insn(block, Insn::SendWithoutBlockDirect { recv, cd, cme, iseq, args: processed_args, kw_bits, state: send_state }); + let send_direct = self.push_insn(block, Insn::SendDirect { recv, cd, cme, iseq, args: processed_args, kw_bits, state: send_state, blockiseq: None }); self.make_equal_to(insn_id, send_direct); } else if def_type == VM_METHOD_TYPE_BMETHOD { let procv = unsafe { rb_get_def_bmethod_proc((*cme).def) }; @@ -3083,11 +3101,9 @@ impl Function { let capture = unsafe { proc_block.as_.captured.as_ref() }; let iseq = unsafe { *capture.code.iseq.as_ref() }; - if !can_direct_send(self, block, iseq, ci, insn_id, args.as_slice()) { + if !can_direct_send(self, block, iseq, ci, insn_id, args.as_slice(), None) { self.push_insn_id(block, insn_id); continue; } - // Can't pass a block to a block for now - assert!((unsafe { rb_vm_ci_flag(ci) } & VM_CALL_ARGS_BLOCKARG) == 0, "SendWithoutBlock but has a block arg"); // Patch points: // Check for "defined with an un-shareable Proc in a different Ractor" @@ -3111,7 +3127,7 @@ impl Function { self.push_insn_id(block, insn_id); continue; }; - let send_direct = self.push_insn(block, Insn::SendWithoutBlockDirect { recv, cd, cme, iseq, args: processed_args, kw_bits, state: send_state }); + let send_direct = self.push_insn(block, Insn::SendDirect { recv, cd, cme, iseq, args: processed_args, kw_bits, state: send_state, blockiseq: None }); self.make_equal_to(insn_id, send_direct); } else if def_type == VM_METHOD_TYPE_IVAR && args.is_empty() { // Check if we're accessing ivars of a Class or Module object as they require single-ractor mode. @@ -3240,14 +3256,12 @@ impl Function { self.push_insn_id(block, insn_id); continue; } } - // This doesn't actually optimize Send yet, just replaces the fallback reason to be more precise. - // The actual optimization is done in reduce_send_to_ccall. - Insn::Send { recv, cd, state, .. } => { + Insn::Send { mut recv, cd, state, blockiseq, args, .. } => { let frame_state = self.frame_state(state); - let klass = match self.resolve_receiver_type(recv, self.type_of(recv), frame_state.insn_idx) { - ReceiverTypeResolution::StaticallyKnown { class } => class, + let (klass, profiled_type) = match self.resolve_receiver_type(recv, self.type_of(recv), frame_state.insn_idx) { + ReceiverTypeResolution::StaticallyKnown { class } => (class, None), ReceiverTypeResolution::Monomorphic { profiled_type } - | ReceiverTypeResolution::SkewedPolymorphic { profiled_type } => profiled_type.class(), + | ReceiverTypeResolution::SkewedPolymorphic { profiled_type } => (profiled_type.class(), Some(profiled_type)), ReceiverTypeResolution::SkewedMegamorphic { .. } | ReceiverTypeResolution::Megamorphic => { if get_option!(stats) { @@ -3272,6 +3286,9 @@ impl Function { } }; let ci = unsafe { get_call_data_ci(cd) }; // info about the call site + + let flags = unsafe { rb_vm_ci_flag(ci) }; + let mid = unsafe { vm_ci_mid(ci) }; // Do method lookup let mut cme = unsafe { rb_callable_method_entry(klass, mid) }; @@ -3282,13 +3299,70 @@ impl Function { // Load an overloaded cme if applicable. See vm_search_cc(). // It allows you to use a faster ISEQ if possible. cme = unsafe { rb_check_overloaded_cme(cme, ci) }; + let visibility = unsafe { METHOD_ENTRY_VISI(cme) }; + match (visibility, flags & VM_CALL_FCALL != 0) { + (METHOD_VISI_PUBLIC, _) => {} + (METHOD_VISI_PRIVATE, true) => {} + (METHOD_VISI_PROTECTED, true) => {} + _ => { + self.set_dynamic_send_reason(insn_id, SendNotOptimizedNeedPermission); + self.push_insn_id(block, insn_id); continue; + } + } let mut def_type = unsafe { get_cme_def_type(cme) }; while def_type == VM_METHOD_TYPE_ALIAS { cme = unsafe { rb_aliased_callable_method_entry(cme) }; def_type = unsafe { get_cme_def_type(cme) }; } - self.set_dynamic_send_reason(insn_id, SendNotOptimizedMethodType(MethodType::from(def_type))); - self.push_insn_id(block, insn_id); continue; + + // If the call site info indicates that the `Function` has overly complex arguments, then do not optimize into a `SendDirect`. + // Optimized methods(`VM_METHOD_TYPE_OPTIMIZED`) handle their own argument constraints (e.g., kw_splat for Proc call). + if def_type != VM_METHOD_TYPE_OPTIMIZED && unspecializable_call_type(flags) { + self.count_complex_call_features(block, flags); + self.set_dynamic_send_reason(insn_id, ComplexArgPass); + self.push_insn_id(block, insn_id); continue; + } + + if def_type == VM_METHOD_TYPE_ISEQ { + let iseq = unsafe { get_def_iseq_ptr((*cme).def) }; + if !can_direct_send(self, block, iseq, ci, insn_id, args.as_slice(), Some(blockiseq)) { + self.push_insn_id(block, insn_id); continue; + } + + // Check singleton class assumption first, before emitting other patchpoints + if !self.assume_no_singleton_classes(block, klass, state) { + self.set_dynamic_send_reason(insn_id, SingletonClassSeen); + self.push_insn_id(block, insn_id); continue; + } + + // Add PatchPoint for method redefinition + self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state }); + + // Add GuardType for profiled receiver + if let Some(profiled_type) = profiled_type { + recv = self.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state }); + } + + let Ok((send_state, processed_args, kw_bits)) = self.prepare_direct_send_args(block, &args, ci, iseq, state) + .inspect_err(|&reason| self.set_dynamic_send_reason(insn_id, reason)) else { + self.push_insn_id(block, insn_id); continue; + }; + + let send_direct = self.push_insn(block, Insn::SendDirect { + recv, + cd, + cme, + iseq, + args: processed_args, + kw_bits, + blockiseq: Some(blockiseq), + state: send_state, + }); + self.make_equal_to(insn_id, send_direct); + } else { + self.set_dynamic_send_reason(insn_id, SendNotOptimizedMethodType(MethodType::from(def_type))); + self.push_insn_id(block, insn_id); continue; + } } Insn::GetConstantPath { ic, state, .. } => { let idlist: *const ID = unsafe { (*ic).segments }; @@ -3464,7 +3538,8 @@ impl Function { // Check if the super method's parameters support direct send. // If not, we can't do direct dispatch. let super_iseq = unsafe { get_def_iseq_ptr((*super_cme).def) }; - if !can_direct_send(self, block, super_iseq, ci, insn_id, args.as_slice()) { + // TODO: pass Option to can_direct_send when we start specializing super { ... } + if !can_direct_send(self, block, super_iseq, ci, insn_id, args.as_slice(), None) { self.push_insn_id(block, insn_id); self.set_dynamic_send_reason(insn_id, SuperTargetComplexArgsPass); continue; @@ -3502,15 +3577,16 @@ impl Function { self.push_insn_id(block, insn_id); continue; }; - // Use SendWithoutBlockDirect with the super method's CME and ISEQ. - let send_direct = self.push_insn(block, Insn::SendWithoutBlockDirect { + // Use SendDirect with the super method's CME and ISEQ. + let send_direct = self.push_insn(block, Insn::SendDirect { recv, cd, cme: super_cme, iseq: super_iseq, args: processed_args, kw_bits, - state: send_state + state: send_state, + blockiseq: None, }); self.make_equal_to(insn_id, send_direct); } @@ -3528,7 +3604,7 @@ impl Function { for insn_id in old_insns { match self.find(insn_id) { // Reject block ISEQs to avoid autosplat and other block parameter complications. - Insn::SendWithoutBlockDirect { recv, iseq, cd, args, state, .. } => { + Insn::SendDirect { recv, iseq, cd, args, state, blockiseq: None, .. } => { let call_info = unsafe { (*cd).ci }; let ci_flags = unsafe { vm_ci_flag(call_info) }; // .send call is not currently supported for builtins @@ -3776,7 +3852,7 @@ impl Function { self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass: recv_class, method: method_id, cme }, state }); } - /// Optimize SendWithoutBlock that land in a C method to a direct CCall without + /// Optimize Send/SendWithoutBlock that land in a C method to a direct CCall without /// runtime lookup. fn optimize_c_calls(&mut self) { if unsafe { rb_zjit_method_tracing_currently_enabled() } { @@ -3841,7 +3917,10 @@ impl Function { // When seeing &block argument, fall back to dynamic dispatch for now // TODO: Support block forwarding if unspecializable_c_call_type(ci_flags) { - fun.count_complex_call_features(block, ci_flags); + // Only count features NOT already counted in type_specialize. + if !unspecializable_call_type(ci_flags) { + fun.count_complex_call_features(block, ci_flags); + } fun.set_dynamic_send_reason(send_insn_id, ComplexArgPass); return Err(()); } @@ -4011,7 +4090,10 @@ impl Function { // Filter for simple call sites (i.e. no splats etc.) if ci_flags & VM_CALL_ARGS_SIMPLE == 0 { - fun.count_complex_call_features(block, ci_flags); + // Only count features NOT already counted in type_specialize. + if !unspecializable_call_type(ci_flags) { + fun.count_complex_call_features(block, ci_flags); + } fun.set_dynamic_send_reason(send_insn_id, ComplexArgPass); return Err(()); } @@ -4091,8 +4173,10 @@ impl Function { // func(int argc, VALUE *argv, VALUE recv) let ci_flags = unsafe { vm_ci_flag(call_info) }; if ci_flags & VM_CALL_ARGS_SIMPLE == 0 { - // TODO(alan): Add fun.count_complex_call_features() here without double - // counting splat + // Only count features NOT already counted in type_specialize. + if !unspecializable_call_type(ci_flags) { + fun.count_complex_call_features(block, ci_flags); + } fun.set_dynamic_send_reason(send_insn_id, ComplexArgPass); return Err(()); } else { @@ -4616,7 +4700,7 @@ impl Function { | &Insn::SendWithoutBlock { recv, ref args, state, .. } | &Insn::CCallVariadic { recv, ref args, state, .. } | &Insn::CCallWithFrame { recv, ref args, state, .. } - | &Insn::SendWithoutBlockDirect { recv, ref args, state, .. } + | &Insn::SendDirect { recv, ref args, state, .. } | &Insn::InvokeBuiltin { recv, ref args, state, .. } | &Insn::InvokeSuper { recv, ref args, state, .. } | &Insn::InvokeSuperForward { recv, ref args, state, .. } @@ -5276,7 +5360,7 @@ impl Function { } // Instructions with recv and a Vec of Ruby objects Insn::SendWithoutBlock { recv, ref args, .. } - | Insn::SendWithoutBlockDirect { recv, ref args, .. } + | Insn::SendDirect { recv, ref args, .. } | Insn::Send { recv, ref args, .. } | Insn::SendForward { recv, ref args, .. } | Insn::InvokeSuper { recv, ref args, .. } diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 0110af3f2c4c5d..70afd54022e40e 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -701,7 +701,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v18:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v19:BasicObject = SendWithoutBlockDirect v18, :foo (0x1038) + v19:BasicObject = SendDirect v18, 0x1038, :foo (0x1048) CheckInterrupts Return v19 "); @@ -795,7 +795,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v21:BasicObject = SendWithoutBlockDirect v20, :foo (0x1038), v11 + v21:BasicObject = SendDirect v20, 0x1038, :foo (0x1048), v11 CheckInterrupts Return v21 "); @@ -913,7 +913,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v18:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v19:BasicObject = SendWithoutBlockDirect v18, :foo (0x1038) + v19:BasicObject = SendDirect v18, 0x1038, :foo (0x1048) CheckInterrupts Return v19 "); @@ -941,7 +941,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, Integer@0x1008, cme:0x1010) v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v21:BasicObject = SendWithoutBlockDirect v20, :Integer (0x1038), v11 + v21:BasicObject = SendDirect v20, 0x1038, :Integer (0x1048), v11 CheckInterrupts Return v21 "); @@ -971,7 +971,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v22:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v23:BasicObject = SendWithoutBlockDirect v22, :foo (0x1038), v11, v13 + v23:BasicObject = SendDirect v22, 0x1038, :foo (0x1048), v11, v13 CheckInterrupts Return v23 "); @@ -1001,11 +1001,11 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v23:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v24:BasicObject = SendWithoutBlockDirect v23, :foo (0x1038) + v24:BasicObject = SendDirect v23, 0x1038, :foo (0x1048) PatchPoint NoSingletonClass(Object@0x1000) - PatchPoint MethodRedefined(Object@0x1000, bar@0x1040, cme:0x1048) + PatchPoint MethodRedefined(Object@0x1000, bar@0x1050, cme:0x1058) v27:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v28:BasicObject = SendWithoutBlockDirect v27, :bar (0x1038) + v28:BasicObject = SendDirect v27, 0x1038, :bar (0x1048) CheckInterrupts Return v28 "); @@ -1031,7 +1031,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v18:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v19:BasicObject = SendWithoutBlockDirect v18, :foo (0x1038) + v19:BasicObject = SendDirect v18, 0x1038, :foo (0x1048) CheckInterrupts Return v19 "); @@ -1058,7 +1058,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v21:BasicObject = SendWithoutBlockDirect v20, :foo (0x1038), v11 + v21:BasicObject = SendDirect v20, 0x1038, :foo (0x1048), v11 CheckInterrupts Return v21 "); @@ -1086,7 +1086,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v22:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v23:BasicObject = SendWithoutBlockDirect v22, :foo (0x1038), v11, v13 + v23:BasicObject = SendDirect v22, 0x1038, :foo (0x1048), v11, v13 CheckInterrupts Return v23 "); @@ -1113,14 +1113,14 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, target@0x1008, cme:0x1010) v44:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v45:BasicObject = SendWithoutBlockDirect v44, :target (0x1038) + v45:BasicObject = SendDirect v44, 0x1038, :target (0x1048) v14:Fixnum[10] = Const Value(10) v16:Fixnum[20] = Const Value(20) v18:Fixnum[30] = Const Value(30) PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, target@0x1008, cme:0x1010) v48:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v49:BasicObject = SendWithoutBlockDirect v48, :target (0x1038), v14, v16, v18 + v49:BasicObject = SendDirect v48, 0x1038, :target (0x1048), v14, v16, v18 v24:Fixnum[10] = Const Value(10) v26:Fixnum[20] = Const Value(20) v28:Fixnum[30] = Const Value(30) @@ -2865,20 +2865,21 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(C@0x1000) PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) v21:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - v22:BasicObject = SendWithoutBlockDirect v21, :foo (0x1038) + v22:BasicObject = SendDirect v21, 0x1038, :foo (0x1048) CheckInterrupts Return v22 "); } #[test] - fn dont_specialize_call_to_iseq_with_block() { - eval(" - def foo(&block) = 1 - def test = foo {|| } + fn test_send_direct_iseq_with_block() { + let result = eval(" + def foo(a, b, &block) = block.call(a, b) + def test = foo(1, 2) { |a, b| a + b } test test "); + assert_eq!(VALUE::fixnum_from_usize(3), result); assert_snapshot!(hir_string("test"), @r" fn test@:3: bb0(): @@ -2889,9 +2890,14 @@ mod hir_opt_tests { EntryPoint JIT(0) Jump bb2(v4) bb2(v6:BasicObject): - v11:BasicObject = Send v6, 0x1000, :foo # SendFallbackReason: Send: unsupported method type Iseq + v11:Fixnum[1] = Const Value(1) + v13:Fixnum[2] = Const Value(2) + PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) + v22:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v23:BasicObject = SendDirect v22, 0x1038, :foo (0x1048), v11, v13 CheckInterrupts - Return v11 + Return v23 "); } @@ -2921,7 +2927,10 @@ mod hir_opt_tests { bb2(v8:BasicObject, v9:NilClass): v13:Fixnum[1] = Const Value(1) SetLocal :a, l0, EP@3, v13 - v19:BasicObject = Send v8, 0x1000, :foo # SendFallbackReason: Send: unsupported method type Iseq + PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) + v31:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v8, HeapObject[class_exact*:Object@VALUE(0x1000)] + v32:BasicObject = SendDirect v31, 0x1038, :foo (0x1048) v20:BasicObject = GetLocal :a, l0, EP@3 v24:BasicObject = GetLocal :a, l0, EP@3 CheckInterrupts @@ -3003,7 +3012,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v22:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v23:BasicObject = SendWithoutBlockDirect v22, :foo (0x1038), v11, v13 + v23:BasicObject = SendDirect v22, 0x1038, :foo (0x1048), v11, v13 CheckInterrupts Return v23 "); @@ -3033,7 +3042,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v24:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v26:BasicObject = SendWithoutBlockDirect v24, :foo (0x1038), v13, v15, v11 + v26:BasicObject = SendDirect v24, 0x1038, :foo (0x1048), v13, v15, v11 CheckInterrupts Return v26 "); @@ -3063,7 +3072,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v24:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v26:BasicObject = SendWithoutBlockDirect v24, :foo (0x1038), v11, v15, v13 + v26:BasicObject = SendDirect v24, 0x1038, :foo (0x1048), v11, v15, v13 CheckInterrupts Return v26 "); @@ -3092,7 +3101,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v22:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v23:BasicObject = SendWithoutBlockDirect v22, :foo (0x1038), v11, v13 + v23:BasicObject = SendDirect v22, 0x1038, :foo (0x1048), v11, v13 CheckInterrupts Return v23 "); @@ -3122,7 +3131,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v37:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v38:BasicObject = SendWithoutBlockDirect v37, :foo (0x1038), v11, v13, v15 + v38:BasicObject = SendDirect v37, 0x1038, :foo (0x1048), v11, v13, v15 v20:Fixnum[1] = Const Value(1) v22:Fixnum[2] = Const Value(2) v24:Fixnum[4] = Const Value(4) @@ -3130,7 +3139,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v41:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v43:BasicObject = SendWithoutBlockDirect v41, :foo (0x1038), v20, v22, v26, v24 + v43:BasicObject = SendDirect v41, 0x1038, :foo (0x1048), v20, v22, v26, v24 v30:ArrayExact = NewArray v38, v43 CheckInterrupts Return v30 @@ -3161,7 +3170,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v35:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] v36:Fixnum[4] = Const Value(4) - v38:BasicObject = SendWithoutBlockDirect v35, :foo (0x1038), v11, v13, v36 + v38:BasicObject = SendDirect v35, 0x1038, :foo (0x1048), v11, v13, v36 v18:Fixnum[1] = Const Value(1) v20:Fixnum[2] = Const Value(2) v22:Fixnum[40] = Const Value(40) @@ -3169,7 +3178,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v41:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v43:BasicObject = SendWithoutBlockDirect v41, :foo (0x1038), v18, v20, v24, v22 + v43:BasicObject = SendDirect v41, 0x1038, :foo (0x1048), v18, v20, v24, v22 v28:ArrayExact = NewArray v38, v43 CheckInterrupts Return v28 @@ -3198,7 +3207,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, target@0x1008, cme:0x1010) v48:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v49:BasicObject = SendWithoutBlockDirect v48, :target (0x1038), v11 + v49:BasicObject = SendDirect v48, 0x1038, :target (0x1048), v11 v16:Fixnum[10] = Const Value(10) v18:Fixnum[20] = Const Value(20) v20:Fixnum[30] = Const Value(30) @@ -3206,7 +3215,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, target@0x1008, cme:0x1010) v52:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v53:BasicObject = SendWithoutBlockDirect v52, :target (0x1038), v16, v18, v20, v22 + v53:BasicObject = SendDirect v52, 0x1038, :target (0x1048), v16, v18, v20, v22 v27:Fixnum[10] = Const Value(10) v29:Fixnum[20] = Const Value(20) v31:Fixnum[30] = Const Value(30) @@ -3242,7 +3251,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v21:BasicObject = SendWithoutBlockDirect v20, :foo (0x1038), v11 + v21:BasicObject = SendDirect v20, 0x1038, :foo (0x1048), v11 CheckInterrupts Return v21 "); @@ -3296,7 +3305,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v18:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] v19:Fixnum[1] = Const Value(1) - v21:BasicObject = SendWithoutBlockDirect v18, :foo (0x1038), v19 + v21:BasicObject = SendDirect v18, 0x1038, :foo (0x1048), v19 CheckInterrupts Return v21 "); @@ -3374,6 +3383,7 @@ mod hir_opt_tests { v11:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v12:StringExact = StringCopy v11 v14:Fixnum[1] = Const Value(1) + IncrCounter complex_arg_pass_caller_kwarg v16:BasicObject = SendWithoutBlock v6, :sprintf, v12, v14 # SendFallbackReason: Complex argument passing CheckInterrupts Return v16 @@ -3578,7 +3588,7 @@ mod hir_opt_tests { v49:HeapObject[class_exact:C] = ObjectAllocClass C:VALUE(0x1008) PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, initialize@0x1038, cme:0x1040) - v52:BasicObject = SendWithoutBlockDirect v49, :initialize (0x1068), v16 + v52:BasicObject = SendDirect v49, 0x1068, :initialize (0x1078), v16 CheckInterrupts CheckInterrupts Return v49 @@ -5312,7 +5322,7 @@ mod hir_opt_tests { v13:Fixnum[10] = Const Value(10) PatchPoint NoSingletonClass(Array@0x1008) PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) - v23:BasicObject = SendWithoutBlockDirect v11, :[] (0x1040), v13 + v23:BasicObject = SendDirect v11, 0x1040, :[] (0x1050), v13 CheckInterrupts Return v23 "); @@ -5370,7 +5380,7 @@ mod hir_opt_tests { v11:ArrayExact = ArrayDup v10 PatchPoint NoSingletonClass(Array@0x1008) PatchPoint MethodRedefined(Array@0x1008, max@0x1010, cme:0x1018) - v20:BasicObject = SendWithoutBlockDirect v11, :max (0x1040) + v20:BasicObject = SendDirect v11, 0x1040, :max (0x1050) CheckInterrupts Return v20 "); @@ -6534,15 +6544,16 @@ mod hir_opt_tests { } #[test] - fn test_do_not_optimize_send_to_iseq_method_with_block() { - eval(r#" + fn test_send_direct_iseq_with_block_no_callee_block_param() { + let result = eval(r#" def foo yield 1 end - def test = foo {} + def test = foo { |x| x * 2 } test; test "#); + assert_eq!(VALUE::fixnum_from_usize(2), result); assert_snapshot!(hir_string("test"), @r" fn test@:6: bb0(): @@ -6553,9 +6564,12 @@ mod hir_opt_tests { EntryPoint JIT(0) Jump bb2(v4) bb2(v6:BasicObject): - v11:BasicObject = Send v6, 0x1000, :foo # SendFallbackReason: Send: unsupported method type Iseq + PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) + v18:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v19:BasicObject = SendDirect v18, 0x1038, :foo (0x1048) CheckInterrupts - Return v11 + Return v19 "); } @@ -11029,8 +11043,8 @@ mod hir_opt_tests { // A Ruby method as the target of `super` should optimize provided no block is given. let hir = hir_string_proc("B.new.method(:foo)"); - assert!(!hir.contains("InvokeSuper "), "InvokeSuper should optimize to SendWithoutBlockDirect but got:\n{hir}"); - assert!(hir.contains("SendWithoutBlockDirect"), "Should optimize to SendWithoutBlockDirect for call without args or block:\n{hir}"); + assert!(!hir.contains("InvokeSuper "), "InvokeSuper should optimize to SendDirect but got:\n{hir}"); + assert!(hir.contains("SendDirect"), "Should optimize to SendDirect for call without args or block:\n{hir}"); assert_snapshot!(hir, @r" fn foo@:10: @@ -11047,7 +11061,7 @@ mod hir_opt_tests { GuardSuperMethodEntry v17, 0x1038 v19:RubyValue = GetBlockHandler v17 v20:FalseClass = GuardBitEquals v19, Value(false) - v21:BasicObject = SendWithoutBlockDirect v6, :foo (0x1040) + v21:BasicObject = SendDirect v6, 0x1040, :foo (0x1050) CheckInterrupts Return v21 "); @@ -11072,8 +11086,8 @@ mod hir_opt_tests { "); let hir = hir_string_proc("B.new.method(:foo)"); - assert!(!hir.contains("InvokeSuper "), "InvokeSuper should optimize to SendWithoutBlockDirect but got:\n{hir}"); - assert!(hir.contains("SendWithoutBlockDirect"), "Should optimize to SendWithoutBlockDirect for call without args or block:\n{hir}"); + assert!(!hir.contains("InvokeSuper "), "InvokeSuper should optimize to SendDirect but got:\n{hir}"); + assert!(hir.contains("SendDirect"), "Should optimize to SendDirect for call without args or block:\n{hir}"); assert_snapshot!(hir, @r" fn foo@:10: @@ -11091,9 +11105,9 @@ mod hir_opt_tests { GuardSuperMethodEntry v26, 0x1038 v28:RubyValue = GetBlockHandler v26 v29:FalseClass = GuardBitEquals v28, Value(false) - v30:BasicObject = SendWithoutBlockDirect v8, :foo (0x1040), v9 + v30:BasicObject = SendDirect v8, 0x1040, :foo (0x1050), v9 v17:Fixnum[1] = Const Value(1) - PatchPoint MethodRedefined(Integer@0x1048, +@0x1050, cme:0x1058) + PatchPoint MethodRedefined(Integer@0x1058, +@0x1060, cme:0x1068) v33:Fixnum = GuardType v30, Fixnum v34:Fixnum = FixnumAdd v33, v17 IncrCounter inline_cfunc_optimized_send_count @@ -11122,7 +11136,7 @@ mod hir_opt_tests { let hir = hir_string_proc("B.new.method(:foo)"); assert!(hir.contains("InvokeSuper "), "Expected unoptimized InvokeSuper but got:\n{hir}"); - assert!(!hir.contains("SendWithoutBlockDirect"), "Should not optimize to SendWithoutBlockDirect for explicit blockarg:\n{hir}"); + assert!(!hir.contains("SendDirect"), "Should not optimize to SendDirect for explicit blockarg:\n{hir}"); assert_snapshot!(hir, @r" fn foo@:10: @@ -11162,9 +11176,9 @@ mod hir_opt_tests { let hir = hir_string_proc("B.new.method(:foo)"); assert!(hir.contains("InvokeSuper "), "Expected unoptimized InvokeSuper but got:\n{hir}"); - assert!(!hir.contains("SendWithoutBlockDirect"), "Should not optimize to SendWithoutBlockDirect for block literal:\n{hir}"); + assert!(!hir.contains("SendDirect"), "Should not optimize to SendDirect for block literal:\n{hir}"); - // With a block, we don't optimize to SendWithoutBlockDirect + // With a block, we don't optimize to SendDirect assert_snapshot!(hir, @r" fn foo@:10: bb0(): @@ -11195,7 +11209,7 @@ mod hir_opt_tests { let hir = hir_string_proc("MyArray.new.method(:length)"); assert!(hir.contains("InvokeSuper "), "Expected unoptimized InvokeSuper but got:\n{hir}"); - assert!(!hir.contains("SendWithoutBlockDirect"), "Should not optimize to SendWithoutBlockDirect for CFUNC:\n{hir}"); + assert!(!hir.contains("SendDirect"), "Should not optimize to SendDirect for CFUNC:\n{hir}"); assert_snapshot!(hir, @r" fn length@:4: @@ -11234,7 +11248,7 @@ mod hir_opt_tests { let hir = hir_string_proc("B.new.method(:foo)"); assert!(hir.contains("InvokeSuper "), "Expected unoptimized InvokeSuper but got:\n{hir}"); - assert!(!hir.contains("SendWithoutBlockDirect"), "Should not optimize to SendWithoutBlockDirect for explicit blockarg:\n{hir}"); + assert!(!hir.contains("SendDirect"), "Should not optimize to SendDirect for explicit blockarg:\n{hir}"); assert_snapshot!(hir, @r" fn foo@:10: @@ -11282,7 +11296,7 @@ mod hir_opt_tests { let hir = hir_string_proc("B.new.method(:foo)"); assert!(hir.contains("InvokeSuper "), "Expected unoptimized InvokeSuper but got:\n{hir}"); - assert!(!hir.contains("SendWithoutBlockDirect"), "Should not optimize to SendWithoutBlockDirect for symbol-to-proc:\n{hir}"); + assert!(!hir.contains("SendDirect"), "Should not optimize to SendDirect for symbol-to-proc:\n{hir}"); assert_snapshot!(hir, @r" fn foo@:10: diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index c21402449f52fb..e0b0129ea1ce5a 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -80,8 +80,8 @@ mod snapshot_tests { PatchPoint MethodRedefined(Object@0x1010, foo@0x1018, cme:0x1020) v24:HeapObject[class_exact*:Object@VALUE(0x1010)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1010)] v25:Any = Snapshot FrameState { pc: 0x1008, stack: [v6, v13, v15, v11], locals: [] } - v26:BasicObject = SendWithoutBlockDirect v24, :foo (0x1048), v13, v15, v11 - v18:Any = Snapshot FrameState { pc: 0x1050, stack: [v26], locals: [] } + v26:BasicObject = SendDirect v24, 0x1048, :foo (0x1058), v13, v15, v11 + v18:Any = Snapshot FrameState { pc: 0x1060, stack: [v26], locals: [] } PatchPoint NoTracePoint CheckInterrupts Return v26 @@ -114,8 +114,8 @@ mod snapshot_tests { PatchPoint NoSingletonClass(Object@0x1010) PatchPoint MethodRedefined(Object@0x1010, foo@0x1018, cme:0x1020) v22:HeapObject[class_exact*:Object@VALUE(0x1010)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1010)] - v23:BasicObject = SendWithoutBlockDirect v22, :foo (0x1048), v11, v13 - v16:Any = Snapshot FrameState { pc: 0x1050, stack: [v23], locals: [] } + v23:BasicObject = SendDirect v22, 0x1048, :foo (0x1058), v11, v13 + v16:Any = Snapshot FrameState { pc: 0x1060, stack: [v23], locals: [] } PatchPoint NoTracePoint CheckInterrupts Return v23 diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 96d75b7aec84b7..bb11b96dd9a403 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -599,10 +599,10 @@ pub fn send_fallback_counter(reason: crate::hir::SendFallbackReason) -> Counter TooManyArgsForLir => send_fallback_too_many_args_for_lir, SendWithoutBlockBopRedefined => send_fallback_send_without_block_bop_redefined, SendWithoutBlockOperandsNotFixnum => send_fallback_send_without_block_operands_not_fixnum, - SendWithoutBlockDirectKeywordMismatch => send_fallback_send_without_block_direct_keyword_mismatch, - SendWithoutBlockDirectKeywordCountMismatch=> send_fallback_send_without_block_direct_keyword_count_mismatch, - SendWithoutBlockDirectMissingKeyword => send_fallback_send_without_block_direct_missing_keyword, - SendWithoutBlockDirectTooManyKeywords => send_fallback_send_without_block_direct_too_many_keywords, + SendDirectKeywordMismatch => send_fallback_send_without_block_direct_keyword_mismatch, + SendDirectKeywordCountMismatch => send_fallback_send_without_block_direct_keyword_count_mismatch, + SendDirectMissingKeyword => send_fallback_send_without_block_direct_missing_keyword, + SendDirectTooManyKeywords => send_fallback_send_without_block_direct_too_many_keywords, SendPolymorphic => send_fallback_send_polymorphic, SendMegamorphic => send_fallback_send_megamorphic, SendNoProfiles => send_fallback_send_no_profiles, From a8b877a843643fbdccd1a42efaf94ad27705dd55 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Wed, 28 Jan 2026 22:30:20 +0100 Subject: [PATCH 03/11] Update to ruby/mspec@5470479 --- spec/mspec/tool/sync/sync-rubyspec.rb | 6 +++--- spec/mspec/tool/tag_from_output.rb | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/mspec/tool/sync/sync-rubyspec.rb b/spec/mspec/tool/sync/sync-rubyspec.rb index 617123733e5442..122de0decba9f6 100644 --- a/spec/mspec/tool/sync/sync-rubyspec.rb +++ b/spec/mspec/tool/sync/sync-rubyspec.rb @@ -190,20 +190,20 @@ def test_new_specs Dir.chdir(SOURCE_REPO) do workflow = YAML.load_file(".github/workflows/ci.yml") job_name = MSPEC ? "test" : "specs" - versions = workflow.dig("jobs", job_name, "strategy", "matrix", "ruby") + versions = workflow.dig("jobs", job_name, "strategy", "matrix", "ruby").map(&:to_s) versions = versions.grep(/^\d+\./) # Test on MRI min_version, max_version = versions.minmax test_command = MSPEC ? "bundle install && bundle exec rspec" : "../mspec/bin/mspec -j" run_test = -> version { - command = "chruby #{version} && #{test_command}" + command = "chruby ruby-#{version} && #{test_command}" sh ENV["SHELL"], "-c", command } run_test[min_version] run_test[max_version] - run_test["ruby-master"] if TEST_MASTER + run_test["master"] if TEST_MASTER end end diff --git a/spec/mspec/tool/tag_from_output.rb b/spec/mspec/tool/tag_from_output.rb index b6b46038556ae1..41aa70f932057f 100755 --- a/spec/mspec/tool/tag_from_output.rb +++ b/spec/mspec/tool/tag_from_output.rb @@ -20,7 +20,7 @@ NUMBER = /^\d+\)$/ ERROR_OR_FAILED = / (ERROR|FAILED)$/ -SPEC_FILE = /^(\/.+_spec\.rb)\:\d+/ +SPEC_FILE = /^((?:\/|[CD]:\/).+_spec\.rb)\:\d+/ output.slice_before(NUMBER).select { |number, *rest| number =~ NUMBER and rest.any? { |line| line =~ ERROR_OR_FAILED } From dbd2ff7adca9b49e4bfa7bc3ec8b83bd437f8cb7 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Wed, 28 Jan 2026 22:30:21 +0100 Subject: [PATCH 04/11] Update to ruby/spec@83e26c9 --- spec/ruby/README.md | 1 + spec/ruby/command_line/dash_r_spec.rb | 5 +- spec/ruby/command_line/syntax_error_spec.rb | 10 +- spec/ruby/core/array/fetch_spec.rb | 8 +- spec/ruby/core/array/pack/c_spec.rb | 18 +- spec/ruby/core/array/pack/shared/basic.rb | 21 +- spec/ruby/core/array/pack/shared/float.rb | 72 +--- spec/ruby/core/array/pack/shared/integer.rb | 114 ++---- spec/ruby/core/array/pack/shared/unicode.rb | 18 +- spec/ruby/core/array/pack/w_spec.rb | 18 +- spec/ruby/core/array/rassoc_spec.rb | 22 +- spec/ruby/core/array/sum_spec.rb | 12 +- .../core/basicobject/instance_eval_spec.rb | 8 +- spec/ruby/core/binding/eval_spec.rb | 14 +- .../builtin_constants_spec.rb | 80 ++-- spec/ruby/core/data/with_spec.rb | 12 +- spec/ruby/core/dir/chdir_spec.rb | 144 ++++--- spec/ruby/core/dir/close_spec.rb | 2 +- spec/ruby/core/dir/fchdir_spec.rb | 108 +++--- spec/ruby/core/dir/for_fd_spec.rb | 110 +++--- .../core/encoding/ascii_compatible_spec.rb | 11 + spec/ruby/core/encoding/dummy_spec.rb | 11 + spec/ruby/core/encoding/replicate_spec.rb | 84 +---- spec/ruby/core/enumerator/each_spec.rb | 15 + .../core/exception/no_method_error_spec.rb | 261 +++++-------- spec/ruby/core/false/singleton_method_spec.rb | 14 +- spec/ruby/core/fiber/kill_spec.rb | 120 +++--- spec/ruby/core/fiber/storage_spec.rb | 12 +- spec/ruby/core/file/basename_spec.rb | 6 +- spec/ruby/core/file/dirname_spec.rb | 33 ++ spec/ruby/core/float/ceil_spec.rb | 4 +- spec/ruby/core/float/floor_spec.rb | 4 +- spec/ruby/core/float/round_spec.rb | 69 ++-- spec/ruby/core/gc/config_spec.rb | 14 + spec/ruby/core/hash/compact_spec.rb | 38 +- spec/ruby/core/hash/constructor_spec.rb | 30 +- spec/ruby/core/hash/new_spec.rb | 2 +- .../core/hash/ruby2_keywords_hash_spec.rb | 12 +- spec/ruby/core/hash/shared/to_s.rb | 31 ++ spec/ruby/core/integer/ceil_spec.rb | 11 - .../integer/shared/integer_ceil_precision.rb | 25 +- .../integer/shared/integer_floor_precision.rb | 9 +- spec/ruby/core/io/binread_spec.rb | 2 +- spec/ruby/core/io/buffer/empty_spec.rb | 8 +- spec/ruby/core/io/buffer/external_spec.rb | 103 +---- spec/ruby/core/io/buffer/for_spec.rb | 94 +++++ spec/ruby/core/io/buffer/free_spec.rb | 20 +- spec/ruby/core/io/buffer/initialize_spec.rb | 56 ++- spec/ruby/core/io/buffer/internal_spec.rb | 103 +---- spec/ruby/core/io/buffer/map_spec.rb | 357 ++++++++++++++++++ spec/ruby/core/io/buffer/mapped_spec.rb | 103 +---- spec/ruby/core/io/buffer/null_spec.rb | 8 +- spec/ruby/core/io/buffer/private_spec.rb | 120 +----- spec/ruby/core/io/buffer/readonly_spec.rb | 139 +------ spec/ruby/core/io/buffer/resize_spec.rb | 30 +- .../core/io/buffer/shared/null_and_empty.rb | 8 +- spec/ruby/core/io/buffer/shared_spec.rb | 115 +----- spec/ruby/core/io/buffer/string_spec.rb | 62 +++ spec/ruby/core/io/buffer/transfer_spec.rb | 18 +- spec/ruby/core/io/foreach_spec.rb | 14 +- spec/ruby/core/io/gets_spec.rb | 24 +- spec/ruby/core/io/pread_spec.rb | 216 ++++++----- spec/ruby/core/io/pwrite_spec.rb | 104 +++-- spec/ruby/core/io/read_spec.rb | 46 +-- spec/ruby/core/io/readlines_spec.rb | 14 +- spec/ruby/core/io/select_spec.rb | 30 +- spec/ruby/core/io/shared/readlines.rb | 8 +- spec/ruby/core/io/write_spec.rb | 9 +- spec/ruby/core/kernel/Integer_spec.rb | 17 +- spec/ruby/core/kernel/caller_spec.rb | 17 +- spec/ruby/core/kernel/eval_spec.rb | 40 +- spec/ruby/core/kernel/lambda_spec.rb | 52 +-- spec/ruby/core/kernel/open_spec.rb | 14 +- spec/ruby/core/kernel/shared/require.rb | 16 +- spec/ruby/core/kernel/sleep_spec.rb | 28 +- spec/ruby/core/marshal/shared/load.rb | 68 ++-- .../core/matchdata/named_captures_spec.rb | 16 +- spec/ruby/core/math/log10_spec.rb | 4 + .../core/module/set_temporary_name_spec.rb | 218 ++++++----- spec/ruby/core/module/shared/class_eval.rb | 6 +- spec/ruby/core/nil/singleton_method_spec.rb | 14 +- spec/ruby/core/numeric/remainder_spec.rb | 4 +- .../core/objectspace/weakkeymap/clear_spec.rb | 34 +- .../objectspace/weakkeymap/delete_spec.rb | 78 ++-- .../weakkeymap/element_reference_spec.rb | 202 +++++----- .../weakkeymap/element_set_spec.rb | 124 +++--- .../objectspace/weakkeymap/getkey_spec.rb | 38 +- .../objectspace/weakkeymap/inspect_spec.rb | 30 +- .../core/objectspace/weakkeymap/key_spec.rb | 68 ++-- .../core/objectspace/weakmap/delete_spec.rb | 42 +-- spec/ruby/core/proc/clone_spec.rb | 20 +- spec/ruby/core/proc/dup_spec.rb | 18 +- spec/ruby/core/proc/lambda_spec.rb | 7 - spec/ruby/core/process/argv0_spec.rb | 6 +- spec/ruby/core/process/status/bit_and_spec.rb | 2 +- .../core/process/status/right_shift_spec.rb | 2 +- spec/ruby/core/process/warmup_spec.rb | 10 +- spec/ruby/core/range/case_compare_spec.rb | 6 +- spec/ruby/core/range/overlap_spec.rb | 168 ++++----- spec/ruby/core/range/reverse_each_spec.rb | 172 +++++---- spec/ruby/core/range/to_set_spec.rb | 59 ++- spec/ruby/core/rational/ceil_spec.rb | 49 +-- spec/ruby/core/rational/exponent_spec.rb | 16 +- spec/ruby/core/rational/floor_spec.rb | 50 +-- .../core/refinement/refined_class_spec.rb | 6 +- spec/ruby/core/refinement/target_spec.rb | 4 +- spec/ruby/core/regexp/linear_time_spec.rb | 6 +- spec/ruby/core/set/flatten_spec.rb | 10 - spec/ruby/core/set/merge_spec.rb | 12 +- spec/ruby/core/set/proper_subset_spec.rb | 10 - spec/ruby/core/set/subset_spec.rb | 10 - spec/ruby/core/string/append_as_bytes_spec.rb | 4 +- spec/ruby/core/string/bytesplice_spec.rb | 316 ++++++++-------- spec/ruby/core/string/index_spec.rb | 29 +- spec/ruby/core/string/shared/chars.rb | 22 +- spec/ruby/core/string/shared/codepoints.rb | 5 + spec/ruby/core/string/shared/each_line.rb | 14 + .../core/string/shared/grapheme_clusters.rb | 9 + spec/ruby/core/string/start_with_spec.rb | 15 +- spec/ruby/core/string/tr_s_spec.rb | 12 +- spec/ruby/core/string/tr_spec.rb | 12 +- spec/ruby/core/string/unpack/b_spec.rb | 36 +- spec/ruby/core/string/unpack/c_spec.rb | 18 +- spec/ruby/core/string/unpack/h_spec.rb | 36 +- spec/ruby/core/string/unpack/shared/basic.rb | 18 +- spec/ruby/core/string/unpack/shared/float.rb | 74 +--- .../ruby/core/string/unpack/shared/integer.rb | 110 ++---- .../ruby/core/string/unpack/shared/unicode.rb | 18 +- spec/ruby/core/string/unpack/w_spec.rb | 18 +- spec/ruby/core/struct/new_spec.rb | 16 +- spec/ruby/core/symbol/inspect_spec.rb | 19 + .../backtrace/location/fixtures/classes.rb | 104 +++++ .../thread/backtrace/location/label_spec.rb | 192 +++++++++- .../ruby/core/thread/native_thread_id_spec.rb | 8 +- spec/ruby/core/time/new_spec.rb | 18 +- spec/ruby/core/tracepoint/path_spec.rb | 31 +- .../core/tracepoint/raised_exception_spec.rb | 26 +- spec/ruby/core/true/singleton_method_spec.rb | 14 +- .../core/unboundmethod/equal_value_spec.rb | 3 - .../core/warning/element_reference_spec.rb | 8 +- spec/ruby/core/warning/element_set_spec.rb | 16 +- spec/ruby/language/assignments_spec.rb | 10 +- spec/ruby/language/block_spec.rb | 53 ++- spec/ruby/language/delegation_spec.rb | 46 +-- spec/ruby/language/file_spec.rb | 12 +- spec/ruby/language/for_spec.rb | 55 ++- spec/ruby/language/hash_spec.rb | 29 +- spec/ruby/language/it_parameter_spec.rb | 46 ++- spec/ruby/language/keyword_arguments_spec.rb | 16 +- spec/ruby/language/method_spec.rb | 6 +- spec/ruby/library/English/English_spec.rb | 12 - .../ruby/library/bigdecimal/remainder_spec.rb | 19 - spec/ruby/library/bigdecimal/to_s_spec.rb | 6 +- .../random/formatter/alphanumeric_spec.rb | 18 +- spec/ruby/library/ripper/lex_spec.rb | 6 +- .../socket/addrinfo/initialize_spec.rb | 36 +- .../socket/basicsocket/recv_nonblock_spec.rb | 66 +--- .../library/socket/basicsocket/recv_spec.rb | 43 +-- .../basicsocket/recvmsg_nonblock_spec.rb | 69 +--- .../socket/basicsocket/recvmsg_spec.rb | 48 +-- .../library/socket/ipsocket/recvfrom_spec.rb | 44 +-- .../library/socket/socket/getaddrinfo_spec.rb | 22 +- .../library/socket/socket/getnameinfo_spec.rb | 22 +- .../socket/socket/recvfrom_nonblock_spec.rb | 67 +--- .../library/socket/socket/recvfrom_spec.rb | 44 +-- .../stringscanner/named_captures_spec.rb | 8 +- spec/ruby/optional/capi/encoding_spec.rb | 30 ++ spec/ruby/optional/capi/ext/encoding_spec.c | 5 + spec/ruby/optional/capi/ext/kernel_spec.c | 11 + spec/ruby/optional/capi/ext/string_spec.c | 10 + spec/ruby/optional/capi/io_spec.rb | 240 ++++++------ spec/ruby/optional/capi/kernel_spec.rb | 6 + spec/ruby/optional/capi/object_spec.rb | 2 - spec/ruby/optional/capi/spec_helper.rb | 6 +- spec/ruby/optional/capi/string_spec.rb | 125 ++++++ spec/ruby/optional/capi/struct_spec.rb | 142 ++++--- spec/ruby/security/cve_2020_10663_spec.rb | 2 +- spec/ruby/shared/kernel/at_exit.rb | 5 +- spec/ruby/shared/queue/freeze.rb | 18 +- spec/ruby/shared/string/start_with.rb | 12 +- 180 files changed, 3735 insertions(+), 4100 deletions(-) create mode 100644 spec/ruby/core/io/buffer/for_spec.rb create mode 100644 spec/ruby/core/io/buffer/map_spec.rb create mode 100644 spec/ruby/core/io/buffer/string_spec.rb diff --git a/spec/ruby/README.md b/spec/ruby/README.md index 674ada4c9e4cc9..14a0068346fe3d 100644 --- a/spec/ruby/README.md +++ b/spec/ruby/README.md @@ -64,6 +64,7 @@ For older specs try these commits: * Ruby 2.7.8 - [Suite](https://github.com/ruby/spec/commit/93787e6035c925b593a9c0c6fb0e7e07a6f1df1f) using [MSpec](https://github.com/ruby/mspec/commit/1d8cf64722d8a7529f7cd205be5f16a89b7a67fd) * Ruby 3.0.7 - [Suite](https://github.com/ruby/spec/commit/affef93d9940f615e4836f64b011da211f570913) using [MSpec](https://github.com/ruby/mspec/commit/0aabb3e548eb5ea6cad0125f8f46cee34542b6b7) * Ruby 3.1.6 - [Suite](https://github.com/ruby/spec/commit/ec960f2389d1c2265d32397fa8afa6d462014efc) using [MSpec](https://github.com/ruby/mspec/commit/484310dbed35b84c74484fd674602f88c42d063a) +* Ruby 3.2.9 - [Suite](https://github.com/ruby/spec/commit/97f076242b7fc6e60703e6a6053365065cd6fc30) using [MSpec](https://github.com/ruby/mspec/commit/54704795e21128a930af2021c72c49cb87065134) ### Running the specs diff --git a/spec/ruby/command_line/dash_r_spec.rb b/spec/ruby/command_line/dash_r_spec.rb index 9f673c53dcc097..62b8dc001452a7 100644 --- a/spec/ruby/command_line/dash_r_spec.rb +++ b/spec/ruby/command_line/dash_r_spec.rb @@ -16,10 +16,7 @@ out = ruby_exe(fixture(__FILE__, "bad_syntax.rb"), options: "-r #{@test_file}", args: "2>&1", exit_status: 1) $?.should_not.success? out.should include("REQUIRED") - - # it's tempting not to rely on error message and rely only on exception class name, - # but CRuby before 3.2 doesn't print class name for syntax error - out.should include_any_of("syntax error", "SyntaxError") + out.should include("SyntaxError") end it "does not require the file if the main script file does not exist" do diff --git a/spec/ruby/command_line/syntax_error_spec.rb b/spec/ruby/command_line/syntax_error_spec.rb index 9ba87b9e22795b..88864c048ebfee 100644 --- a/spec/ruby/command_line/syntax_error_spec.rb +++ b/spec/ruby/command_line/syntax_error_spec.rb @@ -3,17 +3,11 @@ describe "The interpreter" do it "prints an error when given a file with invalid syntax" do out = ruby_exe(fixture(__FILE__, "bad_syntax.rb"), args: "2>&1", exit_status: 1) - - # it's tempting not to rely on error message and rely only on exception class name, - # but CRuby before 3.2 doesn't print class name for syntax error - out.should include_any_of("syntax error", "SyntaxError") + out.should.include?("SyntaxError") end it "prints an error when given code via -e with invalid syntax" do out = ruby_exe(nil, args: "-e 'a{' 2>&1", exit_status: 1) - - # it's tempting not to rely on error message and rely only on exception class name, - # but CRuby before 3.2 doesn't print class name for syntax error - out.should include_any_of("syntax error", "SyntaxError") + out.should.include?("SyntaxError") end end diff --git a/spec/ruby/core/array/fetch_spec.rb b/spec/ruby/core/array/fetch_spec.rb index b81c0b48d7296d..598b481ba46a11 100644 --- a/spec/ruby/core/array/fetch_spec.rb +++ b/spec/ruby/core/array/fetch_spec.rb @@ -12,9 +12,9 @@ end it "raises an IndexError if there is no element at index" do - -> { [1, 2, 3].fetch(3) }.should raise_error(IndexError) - -> { [1, 2, 3].fetch(-4) }.should raise_error(IndexError) - -> { [].fetch(0) }.should raise_error(IndexError) + -> { [1, 2, 3].fetch(3) }.should raise_error(IndexError, "index 3 outside of array bounds: -3...3") + -> { [1, 2, 3].fetch(-4) }.should raise_error(IndexError, "index -4 outside of array bounds: -3...3") + -> { [].fetch(0) }.should raise_error(IndexError, "index 0 outside of array bounds: 0...0") end it "returns default if there is no element at index if passed a default value" do @@ -50,6 +50,6 @@ def o.to_int(); 5; end end it "raises a TypeError when the passed argument can't be coerced to Integer" do - -> { [].fetch("cat") }.should raise_error(TypeError) + -> { [].fetch("cat") }.should raise_error(TypeError, "no implicit conversion of String into Integer") end end diff --git a/spec/ruby/core/array/pack/c_spec.rb b/spec/ruby/core/array/pack/c_spec.rb index 47b71b663d6a98..7a2b95def87f7f 100644 --- a/spec/ruby/core/array/pack/c_spec.rb +++ b/spec/ruby/core/array/pack/c_spec.rb @@ -45,20 +45,10 @@ [1, 2, 3, 4, 5].pack(pack_format('*')).should == "\x01\x02\x03\x04\x05" end - ruby_version_is ""..."3.3" do - it "ignores NULL bytes between directives" do - suppress_warning do - [1, 2, 3].pack(pack_format("\000", 2)).should == "\x01\x02" - end - end - end - - ruby_version_is "3.3" do - it "raise ArgumentError for NULL bytes between directives" do - -> { - [1, 2, 3].pack(pack_format("\000", 2)) - }.should raise_error(ArgumentError, /unknown pack directive/) - end + it "raise ArgumentError for NULL bytes between directives" do + -> { + [1, 2, 3].pack(pack_format("\000", 2)) + }.should raise_error(ArgumentError, /unknown pack directive/) end it "ignores spaces between directives" do diff --git a/spec/ruby/core/array/pack/shared/basic.rb b/spec/ruby/core/array/pack/shared/basic.rb index a63f64d296a312..2ebd75f6c5ed79 100644 --- a/spec/ruby/core/array/pack/shared/basic.rb +++ b/spec/ruby/core/array/pack/shared/basic.rb @@ -32,22 +32,11 @@ [@obj, @obj, @obj, @obj].pack("aa #{pack_format} # some comment \n#{pack_format}").should be_an_instance_of(String) end - ruby_version_is ""..."3.3" do - it "warns that a directive is unknown" do - # additional directive ('a') is required for the X directive - -> { [@obj, @obj].pack("a K" + pack_format) }.should complain(/unknown pack directive 'K' in 'a K#{pack_format}'/) - -> { [@obj, @obj].pack("a 0" + pack_format) }.should complain(/unknown pack directive '0' in 'a 0#{pack_format}'/) - -> { [@obj, @obj].pack("a :" + pack_format) }.should complain(/unknown pack directive ':' in 'a :#{pack_format}'/) - end - end - - ruby_version_is "3.3" do - it "raise ArgumentError when a directive is unknown" do - # additional directive ('a') is required for the X directive - -> { [@obj, @obj].pack("a R" + pack_format) }.should raise_error(ArgumentError, /unknown pack directive 'R'/) - -> { [@obj, @obj].pack("a 0" + pack_format) }.should raise_error(ArgumentError, /unknown pack directive '0'/) - -> { [@obj, @obj].pack("a :" + pack_format) }.should raise_error(ArgumentError, /unknown pack directive ':'/) - end + it "raise ArgumentError when a directive is unknown" do + # additional directive ('a') is required for the X directive + -> { [@obj, @obj].pack("a R" + pack_format) }.should raise_error(ArgumentError, /unknown pack directive 'R'/) + -> { [@obj, @obj].pack("a 0" + pack_format) }.should raise_error(ArgumentError, /unknown pack directive '0'/) + -> { [@obj, @obj].pack("a :" + pack_format) }.should raise_error(ArgumentError, /unknown pack directive ':'/) end it "calls #to_str to coerce the directives string" do diff --git a/spec/ruby/core/array/pack/shared/float.rb b/spec/ruby/core/array/pack/shared/float.rb index 76c800b74dc5f1..3f60fee2150b48 100644 --- a/spec/ruby/core/array/pack/shared/float.rb +++ b/spec/ruby/core/array/pack/shared/float.rb @@ -25,20 +25,10 @@ [2.9, 1.4, 8.2].pack(pack_format("*")).should == "\x9a\x999@33\xb3?33\x03A" end - ruby_version_is ""..."3.3" do - it "ignores NULL bytes between directives" do - suppress_warning do - [5.3, 9.2].pack(pack_format("\000", 2)).should == "\x9a\x99\xa9@33\x13A" - end - end - end - - ruby_version_is "3.3" do - it "raise ArgumentError for NULL bytes between directives" do - -> { - [5.3, 9.2].pack(pack_format("\000", 2)) - }.should raise_error(ArgumentError, /unknown pack directive/) - end + it "raise ArgumentError for NULL bytes between directives" do + -> { + [5.3, 9.2].pack(pack_format("\000", 2)) + }.should raise_error(ArgumentError, /unknown pack directive/) end it "ignores spaces between directives" do @@ -105,20 +95,10 @@ [2.9, 1.4, 8.2].pack(pack_format("*")).should == "@9\x99\x9a?\xb333A\x0333" end - ruby_version_is ""..."3.3" do - it "ignores NULL bytes between directives" do - suppress_warning do - [5.3, 9.2].pack(pack_format("\000", 2)).should == "@\xa9\x99\x9aA\x1333" - end - end - end - - ruby_version_is "3.3" do - it "raise ArgumentError for NULL bytes between directives" do - -> { - [5.3, 9.2].pack(pack_format("\000", 2)) - }.should raise_error(ArgumentError, /unknown pack directive/) - end + it "raise ArgumentError for NULL bytes between directives" do + -> { + [5.3, 9.2].pack(pack_format("\000", 2)) + }.should raise_error(ArgumentError, /unknown pack directive/) end it "ignores spaces between directives" do @@ -177,20 +157,10 @@ [2.9, 1.4, 8.2].pack(pack_format("*")).should == "333333\x07@ffffff\xf6?ffffff\x20@" end - ruby_version_is ""..."3.3" do - it "ignores NULL bytes between directives" do - suppress_warning do - [5.3, 9.2].pack(pack_format("\000", 2)).should == "333333\x15@ffffff\x22@" - end - end - end - - ruby_version_is "3.3" do - it "raise ArgumentError for NULL bytes between directives" do - -> { - [5.3, 9.2].pack(pack_format("\000", 2)) - }.should raise_error(ArgumentError, /unknown pack directive/) - end + it "raise ArgumentError for NULL bytes between directives" do + -> { + [5.3, 9.2].pack(pack_format("\000", 2)) + }.should raise_error(ArgumentError, /unknown pack directive/) end it "ignores spaces between directives" do @@ -248,20 +218,10 @@ [2.9, 1.4, 8.2].pack(pack_format("*")).should == "@\x07333333?\xf6ffffff@\x20ffffff" end - ruby_version_is ""..."3.3" do - it "ignores NULL bytes between directives" do - suppress_warning do - [5.3, 9.2].pack(pack_format("\000", 2)).should == "@\x15333333@\x22ffffff" - end - end - end - - ruby_version_is "3.3" do - it "raise ArgumentError for NULL bytes between directives" do - -> { - [5.3, 9.2].pack(pack_format("\000", 2)) - }.should raise_error(ArgumentError, /unknown pack directive/) - end + it "raise ArgumentError for NULL bytes between directives" do + -> { + [5.3, 9.2].pack(pack_format("\000", 2)) + }.should raise_error(ArgumentError, /unknown pack directive/) end it "ignores spaces between directives" do diff --git a/spec/ruby/core/array/pack/shared/integer.rb b/spec/ruby/core/array/pack/shared/integer.rb index 61f7cca184a9b0..ff2ee492016cc4 100644 --- a/spec/ruby/core/array/pack/shared/integer.rb +++ b/spec/ruby/core/array/pack/shared/integer.rb @@ -41,21 +41,10 @@ str.should == "\x78\x65\xcd\xab\x21\x43" end - ruby_version_is ""..."3.3" do - it "ignores NULL bytes between directives" do - suppress_warning do - str = [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2)) - str.should == "\x78\x65\xcd\xab" - end - end - end - - ruby_version_is "3.3" do - it "raise ArgumentError for NULL bytes between directives" do - -> { - [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2)) - }.should raise_error(ArgumentError, /unknown pack directive/) - end + it "raise ArgumentError for NULL bytes between directives" do + -> { + [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2)) + }.should raise_error(ArgumentError, /unknown pack directive/) end it "ignores spaces between directives" do @@ -105,21 +94,10 @@ str.should == "\x65\x78\xab\xcd\x43\x21" end - ruby_version_is ""..."3.3" do - it "ignores NULL bytes between directives" do - suppress_warning do - str = [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2)) - str.should == "\x65\x78\xab\xcd" - end - end - end - - ruby_version_is "3.3" do - it "raise ArgumentError for NULL bytes between directives" do - -> { - [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2)) - }.should raise_error(ArgumentError, /unknown pack directive/) - end + it "raise ArgumentError for NULL bytes between directives" do + -> { + [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2)) + }.should raise_error(ArgumentError, /unknown pack directive/) end it "ignores spaces between directives" do @@ -169,21 +147,10 @@ str.should == "\x78\x65\x43\x12\xcd\xab\xf0\xde\x21\x43\x65\x78" end - ruby_version_is ""..."3.3" do - it "ignores NULL bytes between directives" do - suppress_warning do - str = [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2)) - str.should == "\x78\x65\x43\x12\xcd\xab\xf0\xde" - end - end - end - - ruby_version_is "3.3" do - it "raise ArgumentError for NULL bytes between directives" do - -> { - [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2)) - }.should raise_error(ArgumentError, /unknown pack directive/) - end + it "raise ArgumentError for NULL bytes between directives" do + -> { + [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2)) + }.should raise_error(ArgumentError, /unknown pack directive/) end it "ignores spaces between directives" do @@ -233,21 +200,10 @@ str.should == "\x12\x43\x65\x78\xde\xf0\xab\xcd\x78\x65\x43\x21" end - ruby_version_is ""..."3.3" do - it "ignores NULL bytes between directives" do - suppress_warning do - str = [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2)) - str.should == "\x12\x43\x65\x78\xde\xf0\xab\xcd" - end - end - end - - ruby_version_is "3.3" do - it "raise ArgumentError for NULL bytes between directives" do - -> { - [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2)) - }.should raise_error(ArgumentError, /unknown pack directive/) - end + it "raise ArgumentError for NULL bytes between directives" do + -> { + [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2)) + }.should raise_error(ArgumentError, /unknown pack directive/) end it "ignores spaces between directives" do @@ -357,21 +313,10 @@ str.should == "\x56\x78\x12\x34\xcd\xab\xf0\xde\xf0\xde\xba\xdc\x21\x43\x65\x78" end - ruby_version_is ""..."3.3" do - it "ignores NULL bytes between directives" do - suppress_warning do - str = [0xdef0_abcd_3412_7856, 0x7865_4321_dcba_def0].pack(pack_format("\000", 2)) - str.should == "\x56\x78\x12\x34\xcd\xab\xf0\xde\xf0\xde\xba\xdc\x21\x43\x65\x78" - end - end - end - - ruby_version_is "3.3" do - it "raise ArgumentError for NULL bytes between directives" do - -> { - [0xdef0_abcd_3412_7856, 0x7865_4321_dcba_def0].pack(pack_format("\000", 2)) - }.should raise_error(ArgumentError, /unknown pack directive/) - end + it "raise ArgumentError for NULL bytes between directives" do + -> { + [0xdef0_abcd_3412_7856, 0x7865_4321_dcba_def0].pack(pack_format("\000", 2)) + }.should raise_error(ArgumentError, /unknown pack directive/) end it "ignores spaces between directives" do @@ -429,21 +374,10 @@ str.should == "\xde\xf0\xab\xcd\x34\x12\x78\x56\x78\x65\x43\x21\xdc\xba\xde\xf0" end - ruby_version_is ""..."3.3" do - it "ignores NULL bytes between directives" do - suppress_warning do - str = [0xdef0_abcd_3412_7856, 0x7865_4321_dcba_def0].pack(pack_format("\000", 2)) - str.should == "\xde\xf0\xab\xcd\x34\x12\x78\x56\x78\x65\x43\x21\xdc\xba\xde\xf0" - end - end - end - - ruby_version_is "3.3" do - it "raise ArgumentError for NULL bytes between directives" do - -> { - [0xdef0_abcd_3412_7856, 0x7865_4321_dcba_def0].pack(pack_format("\000", 2)) - }.should raise_error(ArgumentError, /unknown pack directive/) - end + it "raise ArgumentError for NULL bytes between directives" do + -> { + [0xdef0_abcd_3412_7856, 0x7865_4321_dcba_def0].pack(pack_format("\000", 2)) + }.should raise_error(ArgumentError, /unknown pack directive/) end it "ignores spaces between directives" do diff --git a/spec/ruby/core/array/pack/shared/unicode.rb b/spec/ruby/core/array/pack/shared/unicode.rb index 4d8eaef3231067..0eccc7098c7cbf 100644 --- a/spec/ruby/core/array/pack/shared/unicode.rb +++ b/spec/ruby/core/array/pack/shared/unicode.rb @@ -67,20 +67,10 @@ -> { [obj].pack("U") }.should raise_error(TypeError) end - ruby_version_is ""..."3.3" do - it "ignores NULL bytes between directives" do - suppress_warning do - [1, 2, 3].pack("U\x00U").should == "\x01\x02" - end - end - end - - ruby_version_is "3.3" do - it "raise ArgumentError for NULL bytes between directives" do - -> { - [1, 2, 3].pack("U\x00U") - }.should raise_error(ArgumentError, /unknown pack directive/) - end + it "raise ArgumentError for NULL bytes between directives" do + -> { + [1, 2, 3].pack("U\x00U") + }.should raise_error(ArgumentError, /unknown pack directive/) end it "ignores spaces between directives" do diff --git a/spec/ruby/core/array/pack/w_spec.rb b/spec/ruby/core/array/pack/w_spec.rb index e770288d67b4d2..ebadb94cab0504 100644 --- a/spec/ruby/core/array/pack/w_spec.rb +++ b/spec/ruby/core/array/pack/w_spec.rb @@ -24,20 +24,10 @@ [obj].pack("w").should == "\x05" end - ruby_version_is ""..."3.3" do - it "ignores NULL bytes between directives" do - suppress_warning do - [1, 2, 3].pack("w\x00w").should == "\x01\x02" - end - end - end - - ruby_version_is "3.3" do - it "raise ArgumentError for NULL bytes between directives" do - -> { - [1, 2, 3].pack("w\x00w") - }.should raise_error(ArgumentError, /unknown pack directive/) - end + it "raise ArgumentError for NULL bytes between directives" do + -> { + [1, 2, 3].pack("w\x00w") + }.should raise_error(ArgumentError, /unknown pack directive/) end it "ignores spaces between directives" do diff --git a/spec/ruby/core/array/rassoc_spec.rb b/spec/ruby/core/array/rassoc_spec.rb index 632a05e8b3778b..a7ffb75fb53521 100644 --- a/spec/ruby/core/array/rassoc_spec.rb +++ b/spec/ruby/core/array/rassoc_spec.rb @@ -36,17 +36,15 @@ def o.==(other); other == 'foobar'; end [[1, :foobar, o], [2, o, 1], [3, mock('foo')]].rassoc(key).should == [2, o, 1] end - ruby_version_is "3.3" do - it "calls to_ary on non-array elements" do - s1 = [1, 2] - s2 = ArraySpecs::ArrayConvertible.new(2, 3) - a = [s1, s2] - - s1.should_not_receive(:to_ary) - a.rassoc(2).should equal(s1) - - a.rassoc(3).should == [2, 3] - s2.called.should equal(:to_ary) - end + it "calls to_ary on non-array elements" do + s1 = [1, 2] + s2 = ArraySpecs::ArrayConvertible.new(2, 3) + a = [s1, s2] + + s1.should_not_receive(:to_ary) + a.rassoc(2).should equal(s1) + + a.rassoc(3).should == [2, 3] + s2.called.should equal(:to_ary) end end diff --git a/spec/ruby/core/array/sum_spec.rb b/spec/ruby/core/array/sum_spec.rb index 06abe061359faa..1886d692faaddc 100644 --- a/spec/ruby/core/array/sum_spec.rb +++ b/spec/ruby/core/array/sum_spec.rb @@ -74,13 +74,11 @@ [b].sum(a).should == 42 end - ruby_bug '#19530', ''...'3.3' do - it "calls + on the init value" do - a = mock("a") - b = mock("b") - a.should_receive(:+).with(42).and_return(b) - [42].sum(a).should == b - end + it "calls + on the init value" do + a = mock("a") + b = mock("b") + a.should_receive(:+).with(42).and_return(b) + [42].sum(a).should == b end end diff --git a/spec/ruby/core/basicobject/instance_eval_spec.rb b/spec/ruby/core/basicobject/instance_eval_spec.rb index 633b5c2cb1d9bb..f8d9d7505920d1 100644 --- a/spec/ruby/core/basicobject/instance_eval_spec.rb +++ b/spec/ruby/core/basicobject/instance_eval_spec.rb @@ -84,11 +84,9 @@ def foo end - ruby_version_is "3.3" do - it "uses the caller location as default location" do - f = Object.new - f.instance_eval("[__FILE__, __LINE__]").should == ["(eval at #{__FILE__}:#{__LINE__})", 1] - end + it "uses the caller location as default location" do + f = Object.new + f.instance_eval("[__FILE__, __LINE__]").should == ["(eval at #{__FILE__}:#{__LINE__})", 1] end it "has access to receiver's instance variables" do diff --git a/spec/ruby/core/binding/eval_spec.rb b/spec/ruby/core/binding/eval_spec.rb index bb2036f73911c4..7852e1c93936b4 100644 --- a/spec/ruby/core/binding/eval_spec.rb +++ b/spec/ruby/core/binding/eval_spec.rb @@ -60,14 +60,6 @@ bind.eval("#foo\n__LINE__", "(test)", 88).should == 89 end - ruby_version_is ""..."3.3" do - it "uses (eval) as __FILE__ if single argument given" do - obj = BindingSpecs::Demo.new(1) - bind = obj.get_binding - bind.eval("__FILE__").should == '(eval)' - end - end - it "uses 1 as __LINE__" do obj = BindingSpecs::Demo.new(1) bind = obj.get_binding @@ -107,9 +99,7 @@ bind.eval("'bar'.foo").should == "foo" end - ruby_version_is "3.3" do - it "uses the caller location as default filename" do - binding.eval("[__FILE__, __LINE__]").should == ["(eval at #{__FILE__}:#{__LINE__})", 1] - end + it "uses the caller location as default filename" do + binding.eval("[__FILE__, __LINE__]").should == ["(eval at #{__FILE__}:#{__LINE__})", 1] end end diff --git a/spec/ruby/core/builtin_constants/builtin_constants_spec.rb b/spec/ruby/core/builtin_constants/builtin_constants_spec.rb index 13e066cc7f1664..2c71b416679749 100644 --- a/spec/ruby/core/builtin_constants/builtin_constants_spec.rb +++ b/spec/ruby/core/builtin_constants/builtin_constants_spec.rb @@ -87,65 +87,63 @@ end ruby_version_is "4.0" do - context "The constant" do - describe "Ruby" do - it "is a Module" do - Ruby.should.instance_of?(Module) - end + describe "Ruby" do + it "is a Module" do + Ruby.should.instance_of?(Module) end + end - describe "Ruby::VERSION" do - it "is equal to RUBY_VERSION" do - Ruby::VERSION.should equal(RUBY_VERSION) - end + describe "Ruby::VERSION" do + it "is equal to RUBY_VERSION" do + Ruby::VERSION.should equal(RUBY_VERSION) end + end - describe "RUBY::PATCHLEVEL" do - it "is equal to RUBY_PATCHLEVEL" do - Ruby::PATCHLEVEL.should equal(RUBY_PATCHLEVEL) - end + describe "RUBY::PATCHLEVEL" do + it "is equal to RUBY_PATCHLEVEL" do + Ruby::PATCHLEVEL.should equal(RUBY_PATCHLEVEL) end + end - describe "Ruby::COPYRIGHT" do - it "is equal to RUBY_COPYRIGHT" do - Ruby::COPYRIGHT.should equal(RUBY_COPYRIGHT) - end + describe "Ruby::COPYRIGHT" do + it "is equal to RUBY_COPYRIGHT" do + Ruby::COPYRIGHT.should equal(RUBY_COPYRIGHT) end + end - describe "Ruby::DESCRIPTION" do - it "is equal to RUBY_DESCRIPTION" do - Ruby::DESCRIPTION.should equal(RUBY_DESCRIPTION) - end + describe "Ruby::DESCRIPTION" do + it "is equal to RUBY_DESCRIPTION" do + Ruby::DESCRIPTION.should equal(RUBY_DESCRIPTION) end + end - describe "Ruby::ENGINE" do - it "is equal to RUBY_ENGINE" do - Ruby::ENGINE.should equal(RUBY_ENGINE) - end + describe "Ruby::ENGINE" do + it "is equal to RUBY_ENGINE" do + Ruby::ENGINE.should equal(RUBY_ENGINE) end + end - describe "Ruby::ENGINE_VERSION" do - it "is equal to RUBY_ENGINE_VERSION" do - Ruby::ENGINE_VERSION.should equal(RUBY_ENGINE_VERSION) - end + describe "Ruby::ENGINE_VERSION" do + it "is equal to RUBY_ENGINE_VERSION" do + Ruby::ENGINE_VERSION.should equal(RUBY_ENGINE_VERSION) end + end - describe "Ruby::PLATFORM" do - it "is equal to RUBY_PLATFORM" do - Ruby::PLATFORM.should equal(RUBY_PLATFORM) - end + describe "Ruby::PLATFORM" do + it "is equal to RUBY_PLATFORM" do + Ruby::PLATFORM.should equal(RUBY_PLATFORM) end + end - describe "Ruby::RELEASE_DATE" do - it "is equal to RUBY_RELEASE_DATE" do - Ruby::RELEASE_DATE.should equal(RUBY_RELEASE_DATE) - end + describe "Ruby::RELEASE_DATE" do + it "is equal to RUBY_RELEASE_DATE" do + Ruby::RELEASE_DATE.should equal(RUBY_RELEASE_DATE) end + end - describe "Ruby::REVISION" do - it "is equal to RUBY_REVISION" do - Ruby::REVISION.should equal(RUBY_REVISION) - end + describe "Ruby::REVISION" do + it "is equal to RUBY_REVISION" do + Ruby::REVISION.should equal(RUBY_REVISION) end end end diff --git a/spec/ruby/core/data/with_spec.rb b/spec/ruby/core/data/with_spec.rb index fd0a99d1fadaab..83cb97fa60777b 100644 --- a/spec/ruby/core/data/with_spec.rb +++ b/spec/ruby/core/data/with_spec.rb @@ -44,14 +44,12 @@ def subclass.new(*) data_copy.unit.should == "m" end - ruby_version_is "3.3" do - it "calls #initialize" do - data = DataSpecs::DataWithOverriddenInitialize.new(42, "m") - ScratchPad.clear + it "calls #initialize" do + data = DataSpecs::DataWithOverriddenInitialize.new(42, "m") + ScratchPad.clear - data.with(amount: 0) + data.with(amount: 0) - ScratchPad.recorded.should == [:initialize, [], {amount: 0, unit: "m"}] - end + ScratchPad.recorded.should == [:initialize, [], {amount: 0, unit: "m"}] end end diff --git a/spec/ruby/core/dir/chdir_spec.rb b/spec/ruby/core/dir/chdir_spec.rb index 015386a9026cf3..fd277e4e1d64fc 100644 --- a/spec/ruby/core/dir/chdir_spec.rb +++ b/spec/ruby/core/dir/chdir_spec.rb @@ -125,96 +125,94 @@ def to_str; DirSpecs.mock_dir; end end end -ruby_version_is '3.3' do - describe "Dir#chdir" do - before :all do - DirSpecs.create_mock_dirs - end +describe "Dir#chdir" do + before :all do + DirSpecs.create_mock_dirs + end - after :all do - DirSpecs.delete_mock_dirs - end + after :all do + DirSpecs.delete_mock_dirs + end - before :each do - @original = Dir.pwd - end + before :each do + @original = Dir.pwd + end - after :each do - Dir.chdir(@original) - end + after :each do + Dir.chdir(@original) + end - it "changes the current working directory to self" do - dir = Dir.new(DirSpecs.mock_dir) - dir.chdir - Dir.pwd.should == DirSpecs.mock_dir - ensure - dir.close - end + it "changes the current working directory to self" do + dir = Dir.new(DirSpecs.mock_dir) + dir.chdir + Dir.pwd.should == DirSpecs.mock_dir + ensure + dir.close + end - it "changes the current working directory to self for duration of the block when a block is given" do - dir = Dir.new(DirSpecs.mock_dir) - pwd_in_block = nil + it "changes the current working directory to self for duration of the block when a block is given" do + dir = Dir.new(DirSpecs.mock_dir) + pwd_in_block = nil - dir.chdir { pwd_in_block = Dir.pwd } + dir.chdir { pwd_in_block = Dir.pwd } - pwd_in_block.should == DirSpecs.mock_dir - Dir.pwd.should == @original - ensure - dir.close - end + pwd_in_block.should == DirSpecs.mock_dir + Dir.pwd.should == @original + ensure + dir.close + end - it "returns 0 when successfully changing directory" do - dir = Dir.new(DirSpecs.mock_dir) - dir.chdir.should == 0 - ensure - dir.close - end + it "returns 0 when successfully changing directory" do + dir = Dir.new(DirSpecs.mock_dir) + dir.chdir.should == 0 + ensure + dir.close + end - it "returns the value of the block when a block is given" do - dir = Dir.new(DirSpecs.mock_dir) - dir.chdir { :block_value }.should == :block_value - ensure - dir.close - end + it "returns the value of the block when a block is given" do + dir = Dir.new(DirSpecs.mock_dir) + dir.chdir { :block_value }.should == :block_value + ensure + dir.close + end + + platform_is_not :windows do + it "does not raise an Errno::ENOENT if the original directory no longer exists" do + dir_name1 = tmp('testdir1') + dir_name2 = tmp('testdir2') + Dir.should_not.exist?(dir_name1) + Dir.should_not.exist?(dir_name2) + Dir.mkdir dir_name1 + Dir.mkdir dir_name2 - platform_is_not :windows do - it "does not raise an Errno::ENOENT if the original directory no longer exists" do - dir_name1 = tmp('testdir1') - dir_name2 = tmp('testdir2') - Dir.should_not.exist?(dir_name1) - Dir.should_not.exist?(dir_name2) - Dir.mkdir dir_name1 - Dir.mkdir dir_name2 - - dir2 = Dir.new(dir_name2) - - begin - Dir.chdir(dir_name1) do - dir2.chdir { Dir.unlink dir_name1 } - end - Dir.pwd.should == @original - ensure - Dir.unlink dir_name1 if Dir.exist?(dir_name1) - Dir.unlink dir_name2 if Dir.exist?(dir_name2) + dir2 = Dir.new(dir_name2) + + begin + Dir.chdir(dir_name1) do + dir2.chdir { Dir.unlink dir_name1 } end + Dir.pwd.should == @original ensure - dir2.close + Dir.unlink dir_name1 if Dir.exist?(dir_name1) + Dir.unlink dir_name2 if Dir.exist?(dir_name2) end + ensure + dir2.close end + end - it "always returns to the original directory when given a block" do - dir = Dir.new(DirSpecs.mock_dir) + it "always returns to the original directory when given a block" do + dir = Dir.new(DirSpecs.mock_dir) - begin - dir.chdir do - raise StandardError, "something bad happened" - end - rescue StandardError + begin + dir.chdir do + raise StandardError, "something bad happened" end - - Dir.pwd.should == @original - ensure - dir.close + rescue StandardError end + + Dir.pwd.should == @original + ensure + dir.close end end diff --git a/spec/ruby/core/dir/close_spec.rb b/spec/ruby/core/dir/close_spec.rb index f7cce318b8b17d..10ad1369c84d2f 100644 --- a/spec/ruby/core/dir/close_spec.rb +++ b/spec/ruby/core/dir/close_spec.rb @@ -24,7 +24,7 @@ dir.close.should == nil end - ruby_version_is '3.3'...'3.4' do + ruby_version_is ''...'3.4' do platform_is_not :windows do it "does not raise an error even if the file descriptor is closed with another Dir instance" do dir = Dir.open DirSpecs.mock_dir diff --git a/spec/ruby/core/dir/fchdir_spec.rb b/spec/ruby/core/dir/fchdir_spec.rb index 52600a95f2b80c..d5e77f7f03f372 100644 --- a/spec/ruby/core/dir/fchdir_spec.rb +++ b/spec/ruby/core/dir/fchdir_spec.rb @@ -1,73 +1,71 @@ require_relative '../../spec_helper' require_relative 'fixtures/common' -ruby_version_is '3.3' do - platform_is_not :windows do - describe "Dir.fchdir" do - before :all do - DirSpecs.create_mock_dirs - end +platform_is_not :windows do + describe "Dir.fchdir" do + before :all do + DirSpecs.create_mock_dirs + end - after :all do - DirSpecs.delete_mock_dirs - end + after :all do + DirSpecs.delete_mock_dirs + end - before :each do - @original = Dir.pwd - end + before :each do + @original = Dir.pwd + end - after :each do - Dir.chdir(@original) - end + after :each do + Dir.chdir(@original) + end - it "changes the current working directory to the directory specified by the integer file descriptor" do - dir = Dir.new(DirSpecs.mock_dir) - Dir.fchdir dir.fileno - Dir.pwd.should == DirSpecs.mock_dir - ensure - dir.close - end + it "changes the current working directory to the directory specified by the integer file descriptor" do + dir = Dir.new(DirSpecs.mock_dir) + Dir.fchdir dir.fileno + Dir.pwd.should == DirSpecs.mock_dir + ensure + dir.close + end - it "returns 0 when successfully changing directory" do - dir = Dir.new(DirSpecs.mock_dir) - Dir.fchdir(dir.fileno).should == 0 - ensure - dir.close - end + it "returns 0 when successfully changing directory" do + dir = Dir.new(DirSpecs.mock_dir) + Dir.fchdir(dir.fileno).should == 0 + ensure + dir.close + end - it "returns the value of the block when a block is given" do - dir = Dir.new(DirSpecs.mock_dir) - Dir.fchdir(dir.fileno) { :block_value }.should == :block_value - ensure - dir.close - end + it "returns the value of the block when a block is given" do + dir = Dir.new(DirSpecs.mock_dir) + Dir.fchdir(dir.fileno) { :block_value }.should == :block_value + ensure + dir.close + end - it "changes to the specified directory for the duration of the block" do - dir = Dir.new(DirSpecs.mock_dir) - Dir.fchdir(dir.fileno) { Dir.pwd }.should == DirSpecs.mock_dir - Dir.pwd.should == @original - ensure - dir.close - end + it "changes to the specified directory for the duration of the block" do + dir = Dir.new(DirSpecs.mock_dir) + Dir.fchdir(dir.fileno) { Dir.pwd }.should == DirSpecs.mock_dir + Dir.pwd.should == @original + ensure + dir.close + end - it "raises a SystemCallError if the file descriptor given is not valid" do - -> { Dir.fchdir(-1) }.should raise_error(SystemCallError, "Bad file descriptor - fchdir") - -> { Dir.fchdir(-1) { } }.should raise_error(SystemCallError, "Bad file descriptor - fchdir") - end + it "raises a SystemCallError if the file descriptor given is not valid" do + -> { Dir.fchdir(-1) }.should raise_error(SystemCallError, "Bad file descriptor - fchdir") + -> { Dir.fchdir(-1) { } }.should raise_error(SystemCallError, "Bad file descriptor - fchdir") + end - it "raises a SystemCallError if the file descriptor given is not for a directory" do - -> { Dir.fchdir $stdout.fileno }.should raise_error(SystemCallError, /(Not a directory|Invalid argument) - fchdir/) - -> { Dir.fchdir($stdout.fileno) { } }.should raise_error(SystemCallError, /(Not a directory|Invalid argument) - fchdir/) - end + it "raises a SystemCallError if the file descriptor given is not for a directory" do + -> { Dir.fchdir $stdout.fileno }.should raise_error(SystemCallError, /(Not a directory|Invalid argument) - fchdir/) + -> { Dir.fchdir($stdout.fileno) { } }.should raise_error(SystemCallError, /(Not a directory|Invalid argument) - fchdir/) end end +end - platform_is :windows do - describe "Dir.fchdir" do - it "raises NotImplementedError" do - -> { Dir.fchdir 1 }.should raise_error(NotImplementedError) - -> { Dir.fchdir(1) { } }.should raise_error(NotImplementedError) - end +platform_is :windows do + describe "Dir.fchdir" do + it "raises NotImplementedError" do + -> { Dir.fchdir 1 }.should raise_error(NotImplementedError) + -> { Dir.fchdir(1) { } }.should raise_error(NotImplementedError) end end end diff --git a/spec/ruby/core/dir/for_fd_spec.rb b/spec/ruby/core/dir/for_fd_spec.rb index ba467f2f86d928..1559e1baa43a6c 100644 --- a/spec/ruby/core/dir/for_fd_spec.rb +++ b/spec/ruby/core/dir/for_fd_spec.rb @@ -2,77 +2,75 @@ require_relative 'fixtures/common' quarantine! do # leads to "Errno::EBADF: Bad file descriptor - closedir" in DirSpecs.delete_mock_dirs -ruby_version_is '3.3' do - platform_is_not :windows do - describe "Dir.for_fd" do - before :all do - DirSpecs.create_mock_dirs - end +platform_is_not :windows do + describe "Dir.for_fd" do + before :all do + DirSpecs.create_mock_dirs + end - after :all do - DirSpecs.delete_mock_dirs - end + after :all do + DirSpecs.delete_mock_dirs + end - before :each do - @original = Dir.pwd - end + before :each do + @original = Dir.pwd + end - after :each do - Dir.chdir(@original) - end + after :each do + Dir.chdir(@original) + end - it "returns a new Dir object representing the directory specified by the given integer directory file descriptor" do - dir = Dir.new(DirSpecs.mock_dir) - dir_new = Dir.for_fd(dir.fileno) + it "returns a new Dir object representing the directory specified by the given integer directory file descriptor" do + dir = Dir.new(DirSpecs.mock_dir) + dir_new = Dir.for_fd(dir.fileno) - dir_new.should.instance_of?(Dir) - dir_new.children.should == dir.children - dir_new.fileno.should == dir.fileno - ensure - dir.close - end + dir_new.should.instance_of?(Dir) + dir_new.children.should == dir.children + dir_new.fileno.should == dir.fileno + ensure + dir.close + end - it "returns a new Dir object without associated path" do - dir = Dir.new(DirSpecs.mock_dir) - dir_new = Dir.for_fd(dir.fileno) + it "returns a new Dir object without associated path" do + dir = Dir.new(DirSpecs.mock_dir) + dir_new = Dir.for_fd(dir.fileno) - dir_new.path.should == nil - ensure - dir.close - end + dir_new.path.should == nil + ensure + dir.close + end - it "calls #to_int to convert a value to an Integer" do - dir = Dir.new(DirSpecs.mock_dir) - obj = mock("fd") - obj.should_receive(:to_int).and_return(dir.fileno) + it "calls #to_int to convert a value to an Integer" do + dir = Dir.new(DirSpecs.mock_dir) + obj = mock("fd") + obj.should_receive(:to_int).and_return(dir.fileno) - dir_new = Dir.for_fd(obj) - dir_new.fileno.should == dir.fileno - ensure - dir.close - end + dir_new = Dir.for_fd(obj) + dir_new.fileno.should == dir.fileno + ensure + dir.close + end - it "raises TypeError when value cannot be converted to Integer" do - -> { - Dir.for_fd(nil) - }.should raise_error(TypeError, "no implicit conversion from nil to integer") - end + it "raises TypeError when value cannot be converted to Integer" do + -> { + Dir.for_fd(nil) + }.should raise_error(TypeError, "no implicit conversion from nil to integer") + end - it "raises a SystemCallError if the file descriptor given is not valid" do - -> { Dir.for_fd(-1) }.should raise_error(SystemCallError, "Bad file descriptor - fdopendir") - end + it "raises a SystemCallError if the file descriptor given is not valid" do + -> { Dir.for_fd(-1) }.should raise_error(SystemCallError, "Bad file descriptor - fdopendir") + end - it "raises a SystemCallError if the file descriptor given is not for a directory" do - -> { Dir.for_fd $stdout.fileno }.should raise_error(SystemCallError, "Not a directory - fdopendir") - end + it "raises a SystemCallError if the file descriptor given is not for a directory" do + -> { Dir.for_fd $stdout.fileno }.should raise_error(SystemCallError, "Not a directory - fdopendir") end end +end - platform_is :windows do - describe "Dir.for_fd" do - it "raises NotImplementedError" do - -> { Dir.for_fd 1 }.should raise_error(NotImplementedError) - end +platform_is :windows do + describe "Dir.for_fd" do + it "raises NotImplementedError" do + -> { Dir.for_fd 1 }.should raise_error(NotImplementedError) end end end diff --git a/spec/ruby/core/encoding/ascii_compatible_spec.rb b/spec/ruby/core/encoding/ascii_compatible_spec.rb index 4804300e855dff..bbcc6add9e4e1b 100644 --- a/spec/ruby/core/encoding/ascii_compatible_spec.rb +++ b/spec/ruby/core/encoding/ascii_compatible_spec.rb @@ -8,4 +8,15 @@ it "returns false if self does not represent an ASCII-compatible encoding" do Encoding::UTF_16LE.ascii_compatible?.should be_false end + + it "returns false for UTF_16 and UTF_32" do + Encoding::UTF_16.should_not.ascii_compatible? + Encoding::UTF_32.should_not.ascii_compatible? + end + + it "is always false for dummy encodings" do + Encoding.list.select(&:dummy?).each do |encoding| + encoding.should_not.ascii_compatible? + end + end end diff --git a/spec/ruby/core/encoding/dummy_spec.rb b/spec/ruby/core/encoding/dummy_spec.rb index 75ffcd5a4ec093..77caebca9a2871 100644 --- a/spec/ruby/core/encoding/dummy_spec.rb +++ b/spec/ruby/core/encoding/dummy_spec.rb @@ -11,4 +11,15 @@ Encoding::CP50221.dummy?.should be_true Encoding::UTF_7.dummy?.should be_true end + + it "returns true for UTF_16 and UTF_32" do + Encoding::UTF_16.should.dummy? + Encoding::UTF_32.should.dummy? + end + + it "implies not #ascii_compatible?" do + Encoding.list.select(&:dummy?).each do |encoding| + encoding.should_not.ascii_compatible? + end + end end diff --git a/spec/ruby/core/encoding/replicate_spec.rb b/spec/ruby/core/encoding/replicate_spec.rb index 2da998837f866b..9fe0ba87478bd0 100644 --- a/spec/ruby/core/encoding/replicate_spec.rb +++ b/spec/ruby/core/encoding/replicate_spec.rb @@ -2,87 +2,7 @@ require_relative '../../spec_helper' describe "Encoding#replicate" do - ruby_version_is ""..."3.3" do - before :all do - @i = 0 - end - - before :each do - @i += 1 - @prefix = "RS#{@i}" - end - - it "returns a replica of ASCII" do - name = @prefix + '-ASCII' - e = suppress_warning { Encoding::ASCII.replicate(name) } - e.name.should == name - Encoding.find(name).should == e - - "a".dup.force_encoding(e).valid_encoding?.should be_true - "\x80".dup.force_encoding(e).valid_encoding?.should be_false - end - - it "returns a replica of UTF-8" do - name = @prefix + 'UTF-8' - e = suppress_warning { Encoding::UTF_8.replicate(name) } - e.name.should == name - Encoding.find(name).should == e - - "a".dup.force_encoding(e).valid_encoding?.should be_true - "\u3042".dup.force_encoding(e).valid_encoding?.should be_true - "\x80".dup.force_encoding(e).valid_encoding?.should be_false - end - - it "returns a replica of UTF-16BE" do - name = @prefix + 'UTF-16-BE' - e = suppress_warning { Encoding::UTF_16BE.replicate(name) } - e.name.should == name - Encoding.find(name).should == e - - "a".dup.force_encoding(e).valid_encoding?.should be_false - "\x30\x42".dup.force_encoding(e).valid_encoding?.should be_true - "\x80".dup.force_encoding(e).valid_encoding?.should be_false - end - - it "returns a replica of ISO-2022-JP" do - name = @prefix + 'ISO-2022-JP' - e = suppress_warning { Encoding::ISO_2022_JP.replicate(name) } - Encoding.find(name).should == e - - e.name.should == name - e.dummy?.should be_true - end - - # NOTE: it's unclear of the value of this (for the complexity cost of it), - # but it is the current CRuby behavior. - it "can be associated with a String" do - name = @prefix + '-US-ASCII' - e = suppress_warning { Encoding::US_ASCII.replicate(name) } - e.name.should == name - Encoding.find(name).should == e - - s = "abc".dup.force_encoding(e) - s.encoding.should == e - s.encoding.name.should == name - end - end - - ruby_version_is ""..."3.3" do - it "warns about deprecation" do - -> { - Encoding::US_ASCII.replicate('MY-US-ASCII') - }.should complain(/warning: Encoding#replicate is deprecated and will be removed in Ruby 3.3; use the original encoding instead/) - end - - it "raises EncodingError if too many encodings" do - code = '1_000.times {|i| Encoding::US_ASCII.replicate("R_#{i}") }' - ruby_exe(code, args: "2>&1", exit_status: 1).should.include?('too many encoding (> 256) (EncodingError)') - end - end - - ruby_version_is "3.3" do - it "has been removed" do - Encoding::US_ASCII.should_not.respond_to?(:replicate, true) - end + it "has been removed" do + Encoding::US_ASCII.should_not.respond_to?(:replicate, true) end end diff --git a/spec/ruby/core/enumerator/each_spec.rb b/spec/ruby/core/enumerator/each_spec.rb index 3af16e5587e466..8c9785cc85fe37 100644 --- a/spec/ruby/core/enumerator/each_spec.rb +++ b/spec/ruby/core/enumerator/each_spec.rb @@ -86,4 +86,19 @@ def object_each_with_arguments.each_with_arguments(arg, *args) ret.should be_an_instance_of(Enumerator) ret.should_not equal(@enum_with_arguments) end + + it "does not destructure yielded array values when chaining each.map" do + result = [[[1]]].each.map { |a, b| [a, b] } + result.should == [[[1], nil]] + end + + it "preserves array values yielded from the enumerator" do + result = [[1, 2]].each.map { |a| a } + result.should == [[1, 2]] + end + + it "allows destructuring to occur in the block, not the enumerator" do + result = [[1, 2]].each.map { |a, b| a } + result.should == [1] + end end diff --git a/spec/ruby/core/exception/no_method_error_spec.rb b/spec/ruby/core/exception/no_method_error_spec.rb index 772c569f67963e..d20878c6e3328d 100644 --- a/spec/ruby/core/exception/no_method_error_spec.rb +++ b/spec/ruby/core/exception/no_method_error_spec.rb @@ -66,204 +66,145 @@ end end - ruby_version_is ""..."3.3" do - it "calls #inspect when calling Exception#message" do - ScratchPad.record [] - test_class = Class.new do - def inspect - ScratchPad << :inspect_called - "" - end - end - instance = test_class.new - - begin - instance.bar - rescue NoMethodError => error - error.message.should =~ /\Aundefined method [`']bar' for :#$/ - ScratchPad.recorded.should == [:inspect_called] - end - end - - it "fallbacks to a simpler representation of the receiver when receiver.inspect raises an exception" do - test_class = Class.new do - def inspect - raise NoMethodErrorSpecs::InstanceException - end - end - instance = test_class.new - - begin - instance.bar - rescue NoMethodError => error - message = error.message - message.should =~ /undefined method.+\bbar\b/ - message.should include test_class.inspect - end - end - - it "uses #name to display the receiver if it is a class" do - klass = Class.new { def self.name; "MyClass"; end } - - begin - klass.foo - rescue NoMethodError => error - error.message.should =~ /\Aundefined method [`']foo' for MyClass:Class$/ - end + it "uses a literal name when receiver is nil" do + begin + nil.foo + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']foo' for nil\Z/ end + end - it "uses #name to display the receiver if it is a module" do - mod = Module.new { def self.name; "MyModule"; end } - - begin - mod.foo - rescue NoMethodError => error - error.message.should =~ /\Aundefined method [`']foo' for MyModule:Module$/ - end + it "uses a literal name when receiver is true" do + begin + true.foo + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']foo' for true\Z/ end end - ruby_version_is "3.3" do - it "uses a literal name when receiver is nil" do - begin - nil.foo - rescue NoMethodError => error - error.message.should =~ /\Aundefined method [`']foo' for nil\Z/ - end + it "uses a literal name when receiver is false" do + begin + false.foo + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']foo' for false\Z/ end + end - it "uses a literal name when receiver is true" do - begin - true.foo - rescue NoMethodError => error - error.message.should =~ /\Aundefined method [`']foo' for true\Z/ - end - end + it "uses #name when receiver is a class" do + klass = Class.new { def self.name; "MyClass"; end } - it "uses a literal name when receiver is false" do - begin - false.foo - rescue NoMethodError => error - error.message.should =~ /\Aundefined method [`']foo' for false\Z/ - end + begin + klass.foo + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']foo' for class MyClass\Z/ end + end - it "uses #name when receiver is a class" do - klass = Class.new { def self.name; "MyClass"; end } + it "uses class' string representation when receiver is an anonymous class" do + klass = Class.new - begin - klass.foo - rescue NoMethodError => error - error.message.should =~ /\Aundefined method [`']foo' for class MyClass\Z/ - end + begin + klass.foo + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']foo' for class #\Z/ end + end - it "uses class' string representation when receiver is an anonymous class" do - klass = Class.new + it "uses class' string representation when receiver is a singleton class" do + obj = Object.new + singleton_class = obj.singleton_class - begin - klass.foo - rescue NoMethodError => error - error.message.should =~ /\Aundefined method [`']foo' for class #\Z/ - end + begin + singleton_class.foo + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']foo' for class #>\Z/ end + end - it "uses class' string representation when receiver is a singleton class" do - obj = Object.new - singleton_class = obj.singleton_class + it "uses #name when receiver is a module" do + mod = Module.new { def self.name; "MyModule"; end } - begin - singleton_class.foo - rescue NoMethodError => error - error.message.should =~ /\Aundefined method [`']foo' for class #>\Z/ - end + begin + mod.foo + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']foo' for module MyModule\Z/ end + end - it "uses #name when receiver is a module" do - mod = Module.new { def self.name; "MyModule"; end } + it "uses module's string representation when receiver is an anonymous module" do + m = Module.new - begin - mod.foo - rescue NoMethodError => error - error.message.should =~ /\Aundefined method [`']foo' for module MyModule\Z/ - end + begin + m.foo + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']foo' for module #\Z/ end + end - it "uses module's string representation when receiver is an anonymous module" do - m = Module.new + it "uses class #name when receiver is an ordinary object" do + klass = Class.new { def self.name; "MyClass"; end } + instance = klass.new - begin - m.foo - rescue NoMethodError => error - error.message.should =~ /\Aundefined method [`']foo' for module #\Z/ - end + begin + instance.bar + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']bar' for an instance of MyClass\Z/ end + end - it "uses class #name when receiver is an ordinary object" do - klass = Class.new { def self.name; "MyClass"; end } - instance = klass.new + it "uses class string representation when receiver is an instance of anonymous class" do + klass = Class.new + instance = klass.new - begin - instance.bar - rescue NoMethodError => error - error.message.should =~ /\Aundefined method [`']bar' for an instance of MyClass\Z/ - end + begin + instance.bar + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']bar' for an instance of #\Z/ end + end - it "uses class string representation when receiver is an instance of anonymous class" do - klass = Class.new - instance = klass.new + it "uses class name when receiver has a singleton class" do + instance = NoMethodErrorSpecs::NoMethodErrorA.new + def instance.foo; end - begin - instance.bar - rescue NoMethodError => error - error.message.should =~ /\Aundefined method [`']bar' for an instance of #\Z/ - end + begin + instance.bar + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']bar' for #\Z/ end + end - it "uses class name when receiver has a singleton class" do - instance = NoMethodErrorSpecs::NoMethodErrorA.new - def instance.foo; end - - begin - instance.bar - rescue NoMethodError => error - error.message.should =~ /\Aundefined method [`']bar' for #\Z/ + it "does not call #inspect when calling Exception#message" do + ScratchPad.record [] + test_class = Class.new do + def inspect + ScratchPad << :inspect_called + "" end end + instance = test_class.new - it "does not call #inspect when calling Exception#message" do - ScratchPad.record [] - test_class = Class.new do - def inspect - ScratchPad << :inspect_called - "" - end - end - instance = test_class.new - - begin - instance.bar - rescue NoMethodError => error - error.message.should =~ /\Aundefined method [`']bar' for an instance of #\Z/ - ScratchPad.recorded.should == [] - end + begin + instance.bar + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']bar' for an instance of #\Z/ + ScratchPad.recorded.should == [] end + end - it "does not truncate long class names" do - class_name = 'ExceptionSpecs::A' + 'a'*100 + it "does not truncate long class names" do + class_name = 'ExceptionSpecs::A' + 'a'*100 - begin - eval <<~RUBY - class #{class_name} - end + begin + eval <<~RUBY + class #{class_name} + end - obj = #{class_name}.new - obj.foo - RUBY - rescue NoMethodError => error - error.message.should =~ /\Aundefined method [`']foo' for an instance of #{class_name}\Z/ - end + obj = #{class_name}.new + obj.foo + RUBY + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']foo' for an instance of #{class_name}\Z/ end end end diff --git a/spec/ruby/core/false/singleton_method_spec.rb b/spec/ruby/core/false/singleton_method_spec.rb index 738794b46c26f8..16dc85d67c64b7 100644 --- a/spec/ruby/core/false/singleton_method_spec.rb +++ b/spec/ruby/core/false/singleton_method_spec.rb @@ -1,15 +1,13 @@ require_relative '../../spec_helper' describe "FalseClass#singleton_method" do - ruby_version_is '3.3' do - it "raises regardless of whether FalseClass defines the method" do + it "raises regardless of whether FalseClass defines the method" do + -> { false.singleton_method(:foo) }.should raise_error(NameError) + begin + def (false).foo; end -> { false.singleton_method(:foo) }.should raise_error(NameError) - begin - def (false).foo; end - -> { false.singleton_method(:foo) }.should raise_error(NameError) - ensure - FalseClass.send(:remove_method, :foo) - end + ensure + FalseClass.send(:remove_method, :foo) end end end diff --git a/spec/ruby/core/fiber/kill_spec.rb b/spec/ruby/core/fiber/kill_spec.rb index 2f4c499280f400..abf23ff17621fa 100644 --- a/spec/ruby/core/fiber/kill_spec.rb +++ b/spec/ruby/core/fiber/kill_spec.rb @@ -2,89 +2,87 @@ require_relative 'fixtures/classes' require_relative '../../shared/kernel/raise' -ruby_version_is "3.3" do - describe "Fiber#kill" do - it "kills a non-resumed fiber" do - fiber = Fiber.new{} +describe "Fiber#kill" do + it "kills a non-resumed fiber" do + fiber = Fiber.new{} - fiber.alive?.should == true + fiber.alive?.should == true - fiber.kill - fiber.alive?.should == false - end - - it "kills a resumed fiber" do - fiber = Fiber.new{while true; Fiber.yield; end} - fiber.resume - - fiber.alive?.should == true + fiber.kill + fiber.alive?.should == false + end - fiber.kill - fiber.alive?.should == false - end + it "kills a resumed fiber" do + fiber = Fiber.new{while true; Fiber.yield; end} + fiber.resume - it "can kill itself" do - fiber = Fiber.new do - Fiber.current.kill - end + fiber.alive?.should == true - fiber.alive?.should == true + fiber.kill + fiber.alive?.should == false + end - fiber.resume - fiber.alive?.should == false + it "can kill itself" do + fiber = Fiber.new do + Fiber.current.kill end - it "kills a resumed fiber from a child" do - parent = Fiber.new do - child = Fiber.new do - parent.kill - parent.alive?.should == true - end + fiber.alive?.should == true + + fiber.resume + fiber.alive?.should == false + end - child.resume + it "kills a resumed fiber from a child" do + parent = Fiber.new do + child = Fiber.new do + parent.kill + parent.alive?.should == true end - parent.resume - parent.alive?.should == false + child.resume end - it "executes the ensure block" do - ensure_executed = false + parent.resume + parent.alive?.should == false + end - fiber = Fiber.new do - while true; Fiber.yield; end - ensure - ensure_executed = true - end + it "executes the ensure block" do + ensure_executed = false - fiber.resume - fiber.kill - ensure_executed.should == true + fiber = Fiber.new do + while true; Fiber.yield; end + ensure + ensure_executed = true end - it "does not execute rescue block" do - rescue_executed = false + fiber.resume + fiber.kill + ensure_executed.should == true + end - fiber = Fiber.new do - while true; Fiber.yield; end - rescue Exception - rescue_executed = true - end + it "does not execute rescue block" do + rescue_executed = false - fiber.resume - fiber.kill - rescue_executed.should == false + fiber = Fiber.new do + while true; Fiber.yield; end + rescue Exception + rescue_executed = true end - it "repeatedly kills a fiber" do - fiber = Fiber.new do - while true; Fiber.yield; end - ensure - while true; Fiber.yield; end - end + fiber.resume + fiber.kill + rescue_executed.should == false + end - fiber.kill - fiber.alive?.should == false + it "repeatedly kills a fiber" do + fiber = Fiber.new do + while true; Fiber.yield; end + ensure + while true; Fiber.yield; end end + + fiber.kill + fiber.alive?.should == false end end diff --git a/spec/ruby/core/fiber/storage_spec.rb b/spec/ruby/core/fiber/storage_spec.rb index 015caaf3bbff48..6ffc13ee283bec 100644 --- a/spec/ruby/core/fiber/storage_spec.rb +++ b/spec/ruby/core/fiber/storage_spec.rb @@ -161,13 +161,11 @@ def key.to_str; "Foo"; end -> { Fiber[Object.new] = 44 }.should raise_error(TypeError) end - ruby_version_is "3.3" do - it "deletes the fiber storage key when assigning nil" do - Fiber.new(storage: {life: 42}) { - Fiber[:life] = nil - Fiber.current.storage - }.resume.should == {} - end + it "deletes the fiber storage key when assigning nil" do + Fiber.new(storage: {life: 42}) { + Fiber[:life] = nil + Fiber.current.storage + }.resume.should == {} end end diff --git a/spec/ruby/core/file/basename_spec.rb b/spec/ruby/core/file/basename_spec.rb index 87695ab97be3ca..66a5b56ed9a11a 100644 --- a/spec/ruby/core/file/basename_spec.rb +++ b/spec/ruby/core/file/basename_spec.rb @@ -162,11 +162,7 @@ it "rejects strings encoded with non ASCII-compatible encodings" do Encoding.list.reject(&:ascii_compatible?).reject(&:dummy?).each do |enc| - begin - path = "/foo/bar".encode(enc) - rescue Encoding::ConverterNotFoundError - next - end + path = "/foo/bar".encode(enc) -> { File.basename(path) diff --git a/spec/ruby/core/file/dirname_spec.rb b/spec/ruby/core/file/dirname_spec.rb index 8e6016ce6fef5b..1b006af7839f17 100644 --- a/spec/ruby/core/file/dirname_spec.rb +++ b/spec/ruby/core/file/dirname_spec.rb @@ -78,7 +78,33 @@ def object.to_int; 2; end File.dirname("foo/../").should == "foo" end + it "rejects strings encoded with non ASCII-compatible encodings" do + Encoding.list.reject(&:ascii_compatible?).reject(&:dummy?).each do |enc| + path = "/foo/bar".encode(enc) + -> { + File.dirname(path) + }.should raise_error(Encoding::CompatibilityError) + end + end + + it "works with all ASCII-compatible encodings" do + Encoding.list.select(&:ascii_compatible?).each do |enc| + File.dirname("/foo/bar".encode(enc)).should == "/foo".encode(enc) + end + end + + it "handles Shift JIS 0x5C (\\) as second byte of a multi-byte sequence" do + # dir/fileソname.txt + path = "dir/file\x83\x5cname.txt".b.force_encoding(Encoding::SHIFT_JIS) + path.valid_encoding?.should be_true + File.dirname(path).should == "dir" + end + platform_is_not :windows do + it "ignores repeated leading / (edge cases on non-windows)" do + File.dirname("/////foo/bar/").should == "/foo" + end + it "returns all the components of filename except the last one (edge cases on non-windows)" do File.dirname('/////').should == '/' File.dirname("//foo//").should == "/" @@ -94,6 +120,13 @@ def object.to_int; 2; end File.dirname("//foo//").should == "//foo" File.dirname('/////').should == '//' end + + it "handles Shift JIS 0x5C (\\) as second byte of a multi-byte sequence (windows)" do + # dir\fileソname.txt + path = "dir\\file\x83\x5cname.txt".b.force_encoding(Encoding::SHIFT_JIS) + path.valid_encoding?.should be_true + File.dirname(path).should == "dir" + end end it "accepts an object that has a #to_path method" do diff --git a/spec/ruby/core/float/ceil_spec.rb b/spec/ruby/core/float/ceil_spec.rb index 75f56102922e82..5236a133f5de18 100644 --- a/spec/ruby/core/float/ceil_spec.rb +++ b/spec/ruby/core/float/ceil_spec.rb @@ -2,7 +2,7 @@ require_relative '../integer/shared/integer_ceil_precision' describe "Float#ceil" do - context "with precision" do + context "with values equal to integers" do it_behaves_like :integer_ceil_precision, :Float end @@ -20,7 +20,9 @@ 2.1679.ceil(0).should eql(3) 214.94.ceil(-1).should eql(220) 7.0.ceil(1).should eql(7.0) + 200.0.ceil(-2).should eql(200) -1.234.ceil(2).should eql(-1.23) 5.123812.ceil(4).should eql(5.1239) + 10.00001.ceil(5).should eql(10.00001) end end diff --git a/spec/ruby/core/float/floor_spec.rb b/spec/ruby/core/float/floor_spec.rb index 8b492ef4732fb7..1fafdadee9b6d7 100644 --- a/spec/ruby/core/float/floor_spec.rb +++ b/spec/ruby/core/float/floor_spec.rb @@ -2,7 +2,7 @@ require_relative '../integer/shared/integer_floor_precision' describe "Float#floor" do - context "with precision" do + context "with values equal to integers" do it_behaves_like :integer_floor_precision, :Float end @@ -20,7 +20,9 @@ 2.1679.floor(0).should eql(2) 214.94.floor(-1).should eql(210) 7.0.floor(1).should eql(7.0) + 200.0.floor(-2).should eql(200) -1.234.floor(2).should eql(-1.24) 5.123812.floor(4).should eql(5.1238) + 10.00001.floor(5).should eql(10.00001) end end diff --git a/spec/ruby/core/float/round_spec.rb b/spec/ruby/core/float/round_spec.rb index 7e8c792051b9d5..3e6575100bd52a 100644 --- a/spec/ruby/core/float/round_spec.rb +++ b/spec/ruby/core/float/round_spec.rb @@ -66,6 +66,7 @@ it "works for corner cases" do 42.0.round(308).should eql(42.0) 1.0e307.round(2).should eql(1.0e307) + 120.0.round(-1).should eql(120) end # redmine:5271 @@ -145,37 +146,35 @@ -4.809999999999999.round(5, half: :even).should eql(-4.81) end - ruby_bug "#19318", ""..."3.3" do - # These numbers are neighbouring floating point numbers round a - # precise value. They test that the rounding modes work correctly - # round that value and precision is not lost which might cause - # incorrect results. - it "does not lose precision during the rounding process" do - 767573.1875850001.round(5, half: nil).should eql(767573.18759) - 767573.1875850001.round(5, half: :up).should eql(767573.18759) - 767573.1875850001.round(5, half: :down).should eql(767573.18759) - 767573.1875850001.round(5, half: :even).should eql(767573.18759) - -767573.1875850001.round(5, half: nil).should eql(-767573.18759) - -767573.1875850001.round(5, half: :up).should eql(-767573.18759) - -767573.1875850001.round(5, half: :down).should eql(-767573.18759) - -767573.1875850001.round(5, half: :even).should eql(-767573.18759) - 767573.187585.round(5, half: nil).should eql(767573.18759) - 767573.187585.round(5, half: :up).should eql(767573.18759) - 767573.187585.round(5, half: :down).should eql(767573.18758) - 767573.187585.round(5, half: :even).should eql(767573.18758) - -767573.187585.round(5, half: nil).should eql(-767573.18759) - -767573.187585.round(5, half: :up).should eql(-767573.18759) - -767573.187585.round(5, half: :down).should eql(-767573.18758) - -767573.187585.round(5, half: :even).should eql(-767573.18758) - 767573.1875849998.round(5, half: nil).should eql(767573.18758) - 767573.1875849998.round(5, half: :up).should eql(767573.18758) - 767573.1875849998.round(5, half: :down).should eql(767573.18758) - 767573.1875849998.round(5, half: :even).should eql(767573.18758) - -767573.1875849998.round(5, half: nil).should eql(-767573.18758) - -767573.1875849998.round(5, half: :up).should eql(-767573.18758) - -767573.1875849998.round(5, half: :down).should eql(-767573.18758) - -767573.1875849998.round(5, half: :even).should eql(-767573.18758) - end + # These numbers are neighbouring floating point numbers round a + # precise value. They test that the rounding modes work correctly + # round that value and precision is not lost which might cause + # incorrect results. + it "does not lose precision during the rounding process" do + 767573.1875850001.round(5, half: nil).should eql(767573.18759) + 767573.1875850001.round(5, half: :up).should eql(767573.18759) + 767573.1875850001.round(5, half: :down).should eql(767573.18759) + 767573.1875850001.round(5, half: :even).should eql(767573.18759) + -767573.1875850001.round(5, half: nil).should eql(-767573.18759) + -767573.1875850001.round(5, half: :up).should eql(-767573.18759) + -767573.1875850001.round(5, half: :down).should eql(-767573.18759) + -767573.1875850001.round(5, half: :even).should eql(-767573.18759) + 767573.187585.round(5, half: nil).should eql(767573.18759) + 767573.187585.round(5, half: :up).should eql(767573.18759) + 767573.187585.round(5, half: :down).should eql(767573.18758) + 767573.187585.round(5, half: :even).should eql(767573.18758) + -767573.187585.round(5, half: nil).should eql(-767573.18759) + -767573.187585.round(5, half: :up).should eql(-767573.18759) + -767573.187585.round(5, half: :down).should eql(-767573.18758) + -767573.187585.round(5, half: :even).should eql(-767573.18758) + 767573.1875849998.round(5, half: nil).should eql(767573.18758) + 767573.1875849998.round(5, half: :up).should eql(767573.18758) + 767573.1875849998.round(5, half: :down).should eql(767573.18758) + 767573.1875849998.round(5, half: :even).should eql(767573.18758) + -767573.1875849998.round(5, half: nil).should eql(-767573.18758) + -767573.1875849998.round(5, half: :up).should eql(-767573.18758) + -767573.1875849998.round(5, half: :down).should eql(-767573.18758) + -767573.1875849998.round(5, half: :even).should eql(-767573.18758) end it "raises FloatDomainError for exceptional values with a half option" do @@ -197,7 +196,13 @@ it "returns 0 for 0 or undefined ndigits" do (0.0).round.should == 0 (-0.0).round(0).should == 0 - (0.0).round(half: :up) == 0 + (0.0).round(half: :up).should == 0 + end + + it "returns 0 for negative ndigits" do + (0.0).round(-1).should == 0 + (-0.0).round(-1).should == 0 + (0.0).round(-1, half: :up).should == 0 end end end diff --git a/spec/ruby/core/gc/config_spec.rb b/spec/ruby/core/gc/config_spec.rb index e20e8e4a16a97f..db452b0907f58b 100644 --- a/spec/ruby/core/gc/config_spec.rb +++ b/spec/ruby/core/gc/config_spec.rb @@ -40,6 +40,20 @@ GC.config.should == previous end + ruby_version_is ""..."4.0" do + it "returns the same as GC.config but without the :implementation key" do + previous = GC.config + GC.config({}).should == previous.except(:implementation) + end + end + + ruby_version_is "4.0" do + it "returns the same as GC.config, including the :implementation key" do + previous = GC.config + GC.config({}).should == previous + end + end + it "raises an ArgumentError if options include global keys" do -> { GC.config(implementation: "default") }.should raise_error(ArgumentError, 'Attempting to set read-only key "Implementation"') end diff --git a/spec/ruby/core/hash/compact_spec.rb b/spec/ruby/core/hash/compact_spec.rb index 13371bce434fc9..48f8bb7cae166c 100644 --- a/spec/ruby/core/hash/compact_spec.rb +++ b/spec/ruby/core/hash/compact_spec.rb @@ -19,28 +19,26 @@ @hash.should == @initial_pairs end - ruby_version_is '3.3' do - it "retains the default value" do - hash = Hash.new(1) - hash.compact.default.should == 1 - hash[:a] = 1 - hash.compact.default.should == 1 - end + it "retains the default value" do + hash = Hash.new(1) + hash.compact.default.should == 1 + hash[:a] = 1 + hash.compact.default.should == 1 + end - it "retains the default_proc" do - pr = proc { |h, k| h[k] = [] } - hash = Hash.new(&pr) - hash.compact.default_proc.should == pr - hash[:a] = 1 - hash.compact.default_proc.should == pr - end + it "retains the default_proc" do + pr = proc { |h, k| h[k] = [] } + hash = Hash.new(&pr) + hash.compact.default_proc.should == pr + hash[:a] = 1 + hash.compact.default_proc.should == pr + end - it "retains compare_by_identity flag" do - hash = {}.compare_by_identity - hash.compact.compare_by_identity?.should == true - hash[:a] = 1 - hash.compact.compare_by_identity?.should == true - end + it "retains compare_by_identity flag" do + hash = {}.compare_by_identity + hash.compact.compare_by_identity?.should == true + hash[:a] = 1 + hash.compact.compare_by_identity?.should == true end end diff --git a/spec/ruby/core/hash/constructor_spec.rb b/spec/ruby/core/hash/constructor_spec.rb index 0f97f7b40e9c2c..301f8675ce27be 100644 --- a/spec/ruby/core/hash/constructor_spec.rb +++ b/spec/ruby/core/hash/constructor_spec.rb @@ -44,23 +44,23 @@ it "raises for elements that are not arrays" do -> { - Hash[[:a]].should == {} - }.should raise_error(ArgumentError) + Hash[[:a]] + }.should raise_error(ArgumentError, "wrong element type Symbol at 0 (expected array)") -> { - Hash[[:nil]].should == {} - }.should raise_error(ArgumentError) + Hash[[nil]] + }.should raise_error(ArgumentError, "wrong element type nil at 0 (expected array)") end it "raises an ArgumentError for arrays of more than 2 elements" do - ->{ Hash[[[:a, :b, :c]]].should == {} }.should raise_error(ArgumentError) + ->{ + Hash[[[:a, :b, :c]]] + }.should raise_error(ArgumentError, "invalid number of elements (3 for 1..2)") end it "raises an ArgumentError when passed a list of value-invalid-pairs in an array" do -> { - -> { - Hash[[[:a, 1], [:b], 42, [:d, 2], [:e, 2, 3], []]] - }.should complain(/ignoring wrong elements/) - }.should raise_error(ArgumentError) + Hash[[[:a, 1], [:b], 42, [:d, 2], [:e, 2, 3], []]] + }.should raise_error(ArgumentError, "wrong element type Integer at 2 (expected array)") end describe "passed a single argument which responds to #to_hash" do @@ -117,13 +117,11 @@ def obj.to_hash() { 1 => 2, 3 => 4 } end Hash[hash].default_proc.should be_nil end - ruby_version_is '3.3' do - it "does not retain compare_by_identity flag" do - hash = { a: 1 }.compare_by_identity - Hash[hash].compare_by_identity?.should == false + it "does not retain compare_by_identity flag" do + hash = { a: 1 }.compare_by_identity + Hash[hash].compare_by_identity?.should == false - hash = {}.compare_by_identity - Hash[hash].compare_by_identity?.should == false - end + hash = {}.compare_by_identity + Hash[hash].compare_by_identity?.should == false end end diff --git a/spec/ruby/core/hash/new_spec.rb b/spec/ruby/core/hash/new_spec.rb index 5ae3e1f98d6205..8de44ec9411deb 100644 --- a/spec/ruby/core/hash/new_spec.rb +++ b/spec/ruby/core/hash/new_spec.rb @@ -34,7 +34,7 @@ -> { Hash.new(nil) { 0 } }.should raise_error(ArgumentError) end - ruby_version_is "3.3"..."3.4" do + ruby_version_is ""..."3.4" do it "emits a deprecation warning if keyword arguments are passed" do -> { Hash.new(unknown: true) }.should complain( Regexp.new(Regexp.escape("Calling Hash.new with keyword arguments is deprecated and will be removed in Ruby 3.4; use Hash.new({ key: value }) instead")) diff --git a/spec/ruby/core/hash/ruby2_keywords_hash_spec.rb b/spec/ruby/core/hash/ruby2_keywords_hash_spec.rb index 7dbb9c0a98351d..ddf9038800005f 100644 --- a/spec/ruby/core/hash/ruby2_keywords_hash_spec.rb +++ b/spec/ruby/core/hash/ruby2_keywords_hash_spec.rb @@ -72,12 +72,10 @@ Hash.ruby2_keywords_hash(hash).default_proc.should == pr end - ruby_version_is '3.3' do - it "retains compare_by_identity_flag" do - hash = {}.compare_by_identity - Hash.ruby2_keywords_hash(hash).compare_by_identity?.should == true - hash[:a] = 1 - Hash.ruby2_keywords_hash(hash).compare_by_identity?.should == true - end + it "retains compare_by_identity_flag" do + hash = {}.compare_by_identity + Hash.ruby2_keywords_hash(hash).compare_by_identity?.should == true + hash[:a] = 1 + Hash.ruby2_keywords_hash(hash).compare_by_identity?.should == true end end diff --git a/spec/ruby/core/hash/shared/to_s.rb b/spec/ruby/core/hash/shared/to_s.rb index e116b8878b9699..38dd2c44360a95 100644 --- a/spec/ruby/core/hash/shared/to_s.rb +++ b/spec/ruby/core/hash/shared/to_s.rb @@ -89,5 +89,36 @@ it "adds quotes to symbol keys that are not valid symbol literals" do { "needs-quotes": 1 }.send(@method).should == '{"needs-quotes": 1}' end + + it "can be evaled" do + no_quote = '{a: 1, a!: 1, a?: 1}' + eval(no_quote).inspect.should == no_quote + [ + '{"": 1}', + '{"0": 1, "!": 1, "%": 1, "&": 1, "*": 1, "+": 1, "-": 1, "/": 1, "<": 1, ">": 1, "^": 1, "`": 1, "|": 1, "~": 1}', + '{"@a": 1, "$a": 1, "+@": 1, "a=": 1, "[]": 1}', + '{"a\"b": 1, "@@a": 1, "<=>": 1, "===": 1, "[]=": 1}', + ].each do |quote| + eval(quote).inspect.should == quote + end + end + + it "can be evaled when Encoding.default_external is changed" do + external = Encoding.default_external + + Encoding.default_external = Encoding::ASCII + utf8_ascii_hash = '{"\\u3042": 1}' + eval(utf8_ascii_hash).inspect.should == utf8_ascii_hash + + Encoding.default_external = Encoding::UTF_8 + utf8_hash = "{\u3042: 1}" + eval(utf8_hash).inspect.should == utf8_hash + + Encoding.default_external = Encoding::Windows_31J + sjis_hash = "{\x87]: 1}".dup.force_encoding('sjis') + eval(sjis_hash).inspect.should == sjis_hash + ensure + Encoding.default_external = external + end end end diff --git a/spec/ruby/core/integer/ceil_spec.rb b/spec/ruby/core/integer/ceil_spec.rb index eb633fba78432f..395be58fbd3f48 100644 --- a/spec/ruby/core/integer/ceil_spec.rb +++ b/spec/ruby/core/integer/ceil_spec.rb @@ -10,15 +10,4 @@ context "with precision" do it_behaves_like :integer_ceil_precision, :Integer end - - context "precision argument specified as part of the ceil method is negative" do - it "returns the smallest integer greater than self with at least precision.abs trailing zeros" do - 18.ceil(-1).should eql(20) - 18.ceil(-2).should eql(100) - 18.ceil(-3).should eql(1000) - -1832.ceil(-1).should eql(-1830) - -1832.ceil(-2).should eql(-1800) - -1832.ceil(-3).should eql(-1000) - end - end end diff --git a/spec/ruby/core/integer/shared/integer_ceil_precision.rb b/spec/ruby/core/integer/shared/integer_ceil_precision.rb index 9f31c2cf615ed0..b23c17937faf0c 100644 --- a/spec/ruby/core/integer/shared/integer_ceil_precision.rb +++ b/spec/ruby/core/integer/shared/integer_ceil_precision.rb @@ -1,6 +1,6 @@ describe :integer_ceil_precision, shared: true do context "precision is zero" do - it "returns integer self" do + it "returns Integer equal to self" do send(@method, 0).ceil(0).should.eql?(0) send(@method, 123).ceil(0).should.eql?(123) send(@method, -123).ceil(0).should.eql?(-123) @@ -23,7 +23,16 @@ send(@method, 0).ceil(-10).should.eql?(0) end - it "returns largest integer less than self with at least precision.abs trailing zeros" do + it "returns Integer equal to self if there are already at least precision.abs trailing zeros" do + send(@method, 10).ceil(-1).should.eql?(10) + send(@method, 100).ceil(-1).should.eql?(100) + send(@method, 100).ceil(-2).should.eql?(100) + send(@method, -10).ceil(-1).should.eql?(-10) + send(@method, -100).ceil(-1).should.eql?(-100) + send(@method, -100).ceil(-2).should.eql?(-100) + end + + it "returns smallest Integer greater than self with at least precision.abs trailing zeros" do send(@method, 123).ceil(-1).should.eql?(130) send(@method, 123).ceil(-2).should.eql?(200) send(@method, 123).ceil(-3).should.eql?(1000) @@ -31,13 +40,15 @@ send(@method, -123).ceil(-1).should.eql?(-120) send(@method, -123).ceil(-2).should.eql?(-100) send(@method, -123).ceil(-3).should.eql?(0) + + send(@method, 100).ceil(-3).should.eql?(1000) + send(@method, -100).ceil(-3).should.eql?(0) end - ruby_bug "#20654", ""..."3.4" do - it "returns 10**precision.abs when precision.abs is larger than the number digits of self" do - send(@method, 123).ceil(-20).should.eql?(100000000000000000000) - send(@method, 123).ceil(-50).should.eql?(100000000000000000000000000000000000000000000000000) - end + # Bug #20654 + it "returns 10**precision.abs when precision.abs has more digits than self" do + send(@method, 123).ceil(-20).should.eql?(100000000000000000000) + send(@method, 123).ceil(-50).should.eql?(100000000000000000000000000000000000000000000000000) end end end diff --git a/spec/ruby/core/integer/shared/integer_floor_precision.rb b/spec/ruby/core/integer/shared/integer_floor_precision.rb index 4c5888c6c4818d..6247907d4cd0b8 100644 --- a/spec/ruby/core/integer/shared/integer_floor_precision.rb +++ b/spec/ruby/core/integer/shared/integer_floor_precision.rb @@ -33,11 +33,10 @@ send(@method, -123).floor(-3).should.eql?(-1000) end - ruby_bug "#20654", ""..."3.4" do - it "returns -(10**precision.abs) when self is negative and precision.abs is larger than the number digits of self" do - send(@method, -123).floor(-20).should.eql?(-100000000000000000000) - send(@method, -123).floor(-50).should.eql?(-100000000000000000000000000000000000000000000000000) - end + # Bug #20654 + it "returns -(10**precision.abs) when self is negative and precision.abs is larger than the number digits of self" do + send(@method, -123).floor(-20).should.eql?(-100000000000000000000) + send(@method, -123).floor(-50).should.eql?(-100000000000000000000000000000000000000000000000000) end end end diff --git a/spec/ruby/core/io/binread_spec.rb b/spec/ruby/core/io/binread_spec.rb index 9e36b84da97350..e4576c1aa1e4f3 100644 --- a/spec/ruby/core/io/binread_spec.rb +++ b/spec/ruby/core/io/binread_spec.rb @@ -45,7 +45,7 @@ -> { IO.binread @fname, 0, -1 }.should raise_error(Errno::EINVAL) end - ruby_version_is "3.3"..."4.0" do + ruby_version_is ""..."4.0" do # https://bugs.ruby-lang.org/issues/19630 it "warns about deprecation given a path with a pipe" do cmd = "|echo ok" diff --git a/spec/ruby/core/io/buffer/empty_spec.rb b/spec/ruby/core/io/buffer/empty_spec.rb index e1fd4ab6a23268..788b23f88f0a48 100644 --- a/spec/ruby/core/io/buffer/empty_spec.rb +++ b/spec/ruby/core/io/buffer/empty_spec.rb @@ -14,11 +14,9 @@ @buffer.empty?.should be_true end - ruby_version_is "3.3" do - it "is true for a 0-length String-backed buffer created with .string" do - IO::Buffer.string(0) do |buffer| - buffer.empty?.should be_true - end + it "is true for a 0-length String-backed buffer created with .string" do + IO::Buffer.string(0) do |buffer| + buffer.empty?.should be_true end end diff --git a/spec/ruby/core/io/buffer/external_spec.rb b/spec/ruby/core/io/buffer/external_spec.rb index 4377a383578167..10bb51053d422d 100644 --- a/spec/ruby/core/io/buffer/external_spec.rb +++ b/spec/ruby/core/io/buffer/external_spec.rb @@ -6,103 +6,18 @@ @buffer = nil end - context "with a buffer created with .new" do - it "is false for an internal buffer" do - @buffer = IO::Buffer.new(4) - @buffer.external?.should be_false - end - - it "is false for a mapped buffer" do - @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) - @buffer.external?.should be_false - end - end - - context "with a file-backed buffer created with .map" do - it "is true for a regular mapping" do - File.open(__FILE__, "r") do |file| - @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) - @buffer.external?.should be_true - end - end - - ruby_version_is "3.3" do - it "is false for a private mapping" do - File.open(__FILE__, "r") do |file| - @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY | IO::Buffer::PRIVATE) - @buffer.external?.should be_false - end - end - end - end - - context "with a String-backed buffer created with .for" do - it "is true for a buffer created without a block" do - @buffer = IO::Buffer.for("test") - @buffer.external?.should be_true - end - - it "is true for a buffer created with a block" do - IO::Buffer.for(+"test") do |buffer| - buffer.external?.should be_true - end - end + it "is true for a buffer with externally-managed memory" do + @buffer = IO::Buffer.for("string") + @buffer.external?.should be_true end - ruby_version_is "3.3" do - context "with a String-backed buffer created with .string" do - it "is true" do - IO::Buffer.string(4) do |buffer| - buffer.external?.should be_true - end - end - end + it "is false for a buffer with self-managed memory" do + @buffer = IO::Buffer.new(12, IO::Buffer::MAPPED) + @buffer.external?.should be_false end - # Always false for slices - context "with a slice of a buffer" do - context "created with .new" do - it "is false when slicing an internal buffer" do - @buffer = IO::Buffer.new(4) - @buffer.slice.external?.should be_false - end - - it "is false when slicing a mapped buffer" do - @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) - @buffer.slice.external?.should be_false - end - end - - context "created with .map" do - it "is false" do - File.open(__FILE__, "r") do |file| - @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) - @buffer.slice.external?.should be_false - end - end - end - - context "created with .for" do - it "is false when slicing a buffer created without a block" do - @buffer = IO::Buffer.for("test") - @buffer.slice.external?.should be_false - end - - it "is false when slicing a buffer created with a block" do - IO::Buffer.for(+"test") do |buffer| - buffer.slice.external?.should be_false - end - end - end - - ruby_version_is "3.3" do - context "created with .string" do - it "is false" do - IO::Buffer.string(4) do |buffer| - buffer.slice.external?.should be_false - end - end - end - end + it "is false for a null buffer" do + @buffer = IO::Buffer.new(0) + @buffer.external?.should be_false end end diff --git a/spec/ruby/core/io/buffer/for_spec.rb b/spec/ruby/core/io/buffer/for_spec.rb new file mode 100644 index 00000000000000..d59a2a033afb3b --- /dev/null +++ b/spec/ruby/core/io/buffer/for_spec.rb @@ -0,0 +1,94 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer.for" do + before :each do + @string = +"för striñg" + end + + after :each do + @buffer&.free + @buffer = nil + end + + context "without a block" do + it "copies string's contents, creating a separate read-only buffer" do + @buffer = IO::Buffer.for(@string) + + @buffer.size.should == @string.bytesize + @buffer.get_string.should == @string.b + + @string[0] = "d" + @buffer.get_string(0, 1).should == "f".b + + -> { @buffer.set_string("d") }.should raise_error(IO::Buffer::AccessError, "Buffer is not writable!") + end + + it "creates an external, read-only buffer" do + @buffer = IO::Buffer.for(@string) + + @buffer.should_not.internal? + @buffer.should_not.mapped? + @buffer.should.external? + + @buffer.should_not.empty? + @buffer.should_not.null? + + @buffer.should_not.shared? + @buffer.should_not.private? + @buffer.should.readonly? + + @buffer.should_not.locked? + @buffer.should.valid? + end + end + + context "with a block" do + it "returns the last value in the block" do + value = + IO::Buffer.for(@string) do |buffer| + buffer.size * 3 + end + value.should == @string.bytesize * 3 + end + + it "frees the buffer at the end of the block" do + IO::Buffer.for(@string) do |buffer| + @buffer = buffer + @buffer.should_not.null? + end + @buffer.should.null? + end + + context "if string is not frozen" do + it "creates a modifiable string-backed buffer" do + IO::Buffer.for(@string) do |buffer| + buffer.size.should == @string.bytesize + buffer.get_string.should == @string.b + + buffer.should_not.readonly? + + buffer.set_string("ghost shell") + @string.should == "ghost shellg" + end + end + + it "locks the original string to prevent modification" do + IO::Buffer.for(@string) do |_buffer| + -> { @string[0] = "t" }.should raise_error(RuntimeError, "can't modify string; temporarily locked") + end + @string[1] = "u" + @string.should == "fur striñg" + end + end + + context "if string is frozen" do + it "creates a read-only string-backed buffer" do + IO::Buffer.for(@string.freeze) do |buffer| + buffer.should.readonly? + + -> { buffer.set_string("ghost shell") }.should raise_error(IO::Buffer::AccessError, "Buffer is not writable!") + end + end + end + end +end diff --git a/spec/ruby/core/io/buffer/free_spec.rb b/spec/ruby/core/io/buffer/free_spec.rb index f3a491897849ae..9a141e11f6b728 100644 --- a/spec/ruby/core/io/buffer/free_spec.rb +++ b/spec/ruby/core/io/buffer/free_spec.rb @@ -49,17 +49,15 @@ end end - ruby_version_is "3.3" do - context "with a String-backed buffer created with .string" do - it "disassociates the buffer from the string and nullifies the buffer" do - string = - IO::Buffer.string(4) do |buffer| - buffer.set_string("meat") - buffer.free - buffer.null?.should be_true - end - string.should == "meat" - end + context "with a String-backed buffer created with .string" do + it "disassociates the buffer from the string and nullifies the buffer" do + string = + IO::Buffer.string(4) do |buffer| + buffer.set_string("meat") + buffer.free + buffer.null?.should be_true + end + string.should == "meat" end end diff --git a/spec/ruby/core/io/buffer/initialize_spec.rb b/spec/ruby/core/io/buffer/initialize_spec.rb index c86d1e7f1d634a..90b501f53d9a92 100644 --- a/spec/ruby/core/io/buffer/initialize_spec.rb +++ b/spec/ruby/core/io/buffer/initialize_spec.rb @@ -14,14 +14,18 @@ it "creates a buffer with default state" do @buffer = IO::Buffer.new + + @buffer.should_not.external? + @buffer.should_not.shared? + @buffer.should_not.private? @buffer.should_not.readonly? @buffer.should_not.empty? @buffer.should_not.null? - # This is run-time state, set by #locked. @buffer.should_not.locked? + @buffer.should.valid? end context "with size argument" do @@ -29,25 +33,24 @@ size = IO::Buffer::PAGE_SIZE - 1 @buffer = IO::Buffer.new(size) @buffer.size.should == size + @buffer.should_not.empty? + @buffer.should.internal? @buffer.should_not.mapped? - @buffer.should_not.empty? end it "creates a new mapped buffer if size is greater than or equal to IO::Buffer::PAGE_SIZE" do size = IO::Buffer::PAGE_SIZE @buffer = IO::Buffer.new(size) @buffer.size.should == size + @buffer.should_not.empty? + @buffer.should_not.internal? @buffer.should.mapped? - @buffer.should_not.empty? end it "creates a null buffer if size is 0" do @buffer = IO::Buffer.new(0) - @buffer.size.should.zero? - @buffer.should_not.internal? - @buffer.should_not.mapped? @buffer.should.null? @buffer.should.empty? end @@ -77,27 +80,40 @@ @buffer.should_not.empty? end + it "allows extra flags" do + @buffer = IO::Buffer.new(10, IO::Buffer::INTERNAL | IO::Buffer::SHARED | IO::Buffer::READONLY) + @buffer.should.internal? + @buffer.should.shared? + @buffer.should.readonly? + end + + it "ignores flags if size is 0" do + @buffer = IO::Buffer.new(0, 0xffff) + @buffer.should.null? + @buffer.should.empty? + + @buffer.should_not.internal? + @buffer.should_not.mapped? + @buffer.should_not.external? + + @buffer.should_not.shared? + @buffer.should_not.readonly? + + @buffer.should_not.locked? + @buffer.should.valid? + end + it "raises IO::Buffer::AllocationError if neither IO::Buffer::MAPPED nor IO::Buffer::INTERNAL is given" do -> { IO::Buffer.new(10, IO::Buffer::READONLY) }.should raise_error(IO::Buffer::AllocationError, "Could not allocate buffer!") -> { IO::Buffer.new(10, 0) }.should raise_error(IO::Buffer::AllocationError, "Could not allocate buffer!") end - ruby_version_is "3.3" do - it "raises ArgumentError if flags is negative" do - -> { IO::Buffer.new(10, -1) }.should raise_error(ArgumentError, "Flags can't be negative!") - end - end - - ruby_version_is ""..."3.3" do - it "raises IO::Buffer::AllocationError with non-Integer flags" do - -> { IO::Buffer.new(10, 0.0) }.should raise_error(IO::Buffer::AllocationError, "Could not allocate buffer!") - end + it "raises ArgumentError if flags is negative" do + -> { IO::Buffer.new(10, -1) }.should raise_error(ArgumentError, "Flags can't be negative!") end - ruby_version_is "3.3" do - it "raises TypeError with non-Integer flags" do - -> { IO::Buffer.new(10, 0.0) }.should raise_error(TypeError, "not an Integer") - end + it "raises TypeError with non-Integer flags" do + -> { IO::Buffer.new(10, 0.0) }.should raise_error(TypeError, "not an Integer") end end end diff --git a/spec/ruby/core/io/buffer/internal_spec.rb b/spec/ruby/core/io/buffer/internal_spec.rb index 409699cc3c9230..40dc633d5d7fd0 100644 --- a/spec/ruby/core/io/buffer/internal_spec.rb +++ b/spec/ruby/core/io/buffer/internal_spec.rb @@ -6,103 +6,18 @@ @buffer = nil end - context "with a buffer created with .new" do - it "is true for an internal buffer" do - @buffer = IO::Buffer.new(4) - @buffer.internal?.should be_true - end - - it "is false for a mapped buffer" do - @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) - @buffer.internal?.should be_false - end - end - - context "with a file-backed buffer created with .map" do - it "is false for a regular mapping" do - File.open(__FILE__, "r") do |file| - @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) - @buffer.internal?.should be_false - end - end - - ruby_version_is "3.3" do - it "is false for a private mapping" do - File.open(__FILE__, "r") do |file| - @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY | IO::Buffer::PRIVATE) - @buffer.internal?.should be_false - end - end - end - end - - context "with a String-backed buffer created with .for" do - it "is false for a buffer created without a block" do - @buffer = IO::Buffer.for("test") - @buffer.internal?.should be_false - end - - it "is false for a buffer created with a block" do - IO::Buffer.for(+"test") do |buffer| - buffer.internal?.should be_false - end - end + it "is true for an internally-allocated buffer" do + @buffer = IO::Buffer.new(12) + @buffer.internal?.should be_true end - ruby_version_is "3.3" do - context "with a String-backed buffer created with .string" do - it "is false" do - IO::Buffer.string(4) do |buffer| - buffer.internal?.should be_false - end - end - end + it "is false for an externally-allocated buffer" do + @buffer = IO::Buffer.new(12, IO::Buffer::MAPPED) + @buffer.internal?.should be_false end - # Always false for slices - context "with a slice of a buffer" do - context "created with .new" do - it "is false when slicing an internal buffer" do - @buffer = IO::Buffer.new(4) - @buffer.slice.internal?.should be_false - end - - it "is false when slicing a mapped buffer" do - @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) - @buffer.slice.internal?.should be_false - end - end - - context "created with .map" do - it "is false" do - File.open(__FILE__, "r") do |file| - @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) - @buffer.slice.internal?.should be_false - end - end - end - - context "created with .for" do - it "is false when slicing a buffer created without a block" do - @buffer = IO::Buffer.for("test") - @buffer.slice.internal?.should be_false - end - - it "is false when slicing a buffer created with a block" do - IO::Buffer.for(+"test") do |buffer| - buffer.slice.internal?.should be_false - end - end - end - - ruby_version_is "3.3" do - context "created with .string" do - it "is false" do - IO::Buffer.string(4) do |buffer| - buffer.slice.internal?.should be_false - end - end - end - end + it "is false for a null buffer" do + @buffer = IO::Buffer.new(0) + @buffer.internal?.should be_false end end diff --git a/spec/ruby/core/io/buffer/map_spec.rb b/spec/ruby/core/io/buffer/map_spec.rb new file mode 100644 index 00000000000000..d980eb0ae0451a --- /dev/null +++ b/spec/ruby/core/io/buffer/map_spec.rb @@ -0,0 +1,357 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer.map" do + before :all do + @big_file_name = tmp("big_file") + # Usually 4 kibibytes + 16 bytes + File.write(@big_file_name, "12345678" * (IO::Buffer::PAGE_SIZE / 8 + 2)) + end + + after :all do + File.delete(@big_file_name) + end + + def open_fixture + File.open("#{__dir__}/../fixtures/read_text.txt", "r+") + end + + def open_big_file_fixture + File.open(@big_file_name, "r+") + end + + after :each do + @buffer&.free + @buffer = nil + @file&.close + @file = nil + end + + it "creates a new buffer mapped from a file" do + @file = open_fixture + @buffer = IO::Buffer.map(@file) + + @buffer.size.should == 9 + @buffer.get_string.should == "abcâdef\n".b + end + + it "allows to close the file after creating buffer, retaining mapping" do + file = open_fixture + @buffer = IO::Buffer.map(file) + file.close + + @buffer.get_string.should == "abcâdef\n".b + end + + it "creates a mapped, external, shared buffer" do + @file = open_fixture + @buffer = IO::Buffer.map(@file) + + @buffer.should_not.internal? + @buffer.should.mapped? + @buffer.should.external? + + @buffer.should_not.empty? + @buffer.should_not.null? + + @buffer.should.shared? + @buffer.should_not.private? + @buffer.should_not.readonly? + + @buffer.should_not.locked? + @buffer.should.valid? + end + + platform_is_not :windows do + it "is shareable across processes" do + file_name = tmp("shared_buffer") + @file = File.open(file_name, "w+") + @file << "I'm private" + @file.rewind + @buffer = IO::Buffer.map(@file) + + IO.popen("-") do |child_pipe| + if child_pipe + # Synchronize on child's output. + child_pipe.readlines.first.chomp.should == @buffer.to_s + @buffer.get_string.should == "I'm shared!" + + @file.read.should == "I'm shared!" + else + @buffer.set_string("I'm shared!") + puts @buffer + end + ensure + child_pipe&.close + end + ensure + File.unlink(file_name) + end + end + + context "with an empty file" do + ruby_version_is ""..."4.0" do + it "raises a SystemCallError" do + @file = File.open("#{__dir__}/../fixtures/empty.txt", "r+") + -> { IO::Buffer.map(@file) }.should raise_error(SystemCallError) + end + end + + ruby_version_is "4.0" do + it "raises ArgumentError" do + @file = File.open("#{__dir__}/../fixtures/empty.txt", "r+") + -> { IO::Buffer.map(@file) }.should raise_error(ArgumentError, "Invalid negative or zero file size!") + end + end + end + + context "with a file opened only for reading" do + it "raises a SystemCallError if no flags are used" do + @file = File.open("#{__dir__}/../fixtures/read_text.txt", "r") + -> { IO::Buffer.map(@file) }.should raise_error(SystemCallError) + end + end + + context "with size argument" do + it "limits the buffer to the specified size in bytes, starting from the start of the file" do + @file = open_fixture + @buffer = IO::Buffer.map(@file, 4) + + @buffer.size.should == 4 + @buffer.get_string.should == "abc\xC3".b + end + + it "maps the whole file if size is nil" do + @file = open_fixture + @buffer = IO::Buffer.map(@file, nil) + + @buffer.size.should == 9 + end + + context "if size is 0" do + ruby_version_is ""..."4.0" do + platform_is_not :windows do + it "raises a SystemCallError" do + @file = open_fixture + -> { IO::Buffer.map(@file, 0) }.should raise_error(SystemCallError) + end + end + end + + ruby_version_is "4.0" do + it "raises ArgumentError" do + @file = open_fixture + -> { IO::Buffer.map(@file, 0) }.should raise_error(ArgumentError, "Size can't be zero!") + end + end + end + + it "raises TypeError if size is not an Integer or nil" do + @file = open_fixture + -> { IO::Buffer.map(@file, "10") }.should raise_error(TypeError, "not an Integer") + -> { IO::Buffer.map(@file, 10.0) }.should raise_error(TypeError, "not an Integer") + end + + it "raises ArgumentError if size is negative" do + @file = open_fixture + -> { IO::Buffer.map(@file, -1) }.should raise_error(ArgumentError, "Size can't be negative!") + end + + ruby_version_is ""..."4.0" do + # May or may not cause a crash on access. + it "is undefined behavior if size is larger than file size" + end + + ruby_version_is "4.0" do + it "raises ArgumentError if size is larger than file size" do + @file = open_fixture + -> { IO::Buffer.map(@file, 8192) }.should raise_error(ArgumentError, "Size can't be larger than file size!") + end + end + end + + context "with size and offset arguments" do + # Neither Windows nor macOS have clear, stable behavior with non-zero offset. + # https://bugs.ruby-lang.org/issues/21700 + platform_is :linux do + context "if offset is an allowed value for system call" do + it "maps the span specified by size starting from the offset" do + @file = open_big_file_fixture + @buffer = IO::Buffer.map(@file, 14, IO::Buffer::PAGE_SIZE) + + @buffer.size.should == 14 + @buffer.get_string(0, 14).should == "12345678123456" + end + + context "if size is nil" do + ruby_version_is ""..."4.0" do + it "maps the rest of the file" do + @file = open_big_file_fixture + @buffer = IO::Buffer.map(@file, nil, IO::Buffer::PAGE_SIZE) + + @buffer.get_string(0, 1).should == "1" + end + + it "incorrectly sets buffer's size to file's full size" do + @file = open_big_file_fixture + @buffer = IO::Buffer.map(@file, nil, IO::Buffer::PAGE_SIZE) + + @buffer.size.should == @file.size + end + end + + ruby_version_is "4.0" do + it "maps the rest of the file" do + @file = open_big_file_fixture + @buffer = IO::Buffer.map(@file, nil, IO::Buffer::PAGE_SIZE) + + @buffer.get_string(0, 1).should == "1" + end + + it "sets buffer's size to file's remaining size" do + @file = open_big_file_fixture + @buffer = IO::Buffer.map(@file, nil, IO::Buffer::PAGE_SIZE) + + @buffer.size.should == (@file.size - IO::Buffer::PAGE_SIZE) + end + end + end + end + end + + it "maps the file from the start if offset is 0" do + @file = open_fixture + @buffer = IO::Buffer.map(@file, 4, 0) + + @buffer.size.should == 4 + @buffer.get_string.should == "abc\xC3".b + end + + ruby_version_is ""..."4.0" do + # May or may not cause a crash on access. + it "is undefined behavior if offset+size is larger than file size" + end + + ruby_version_is "4.0" do + it "raises ArgumentError if offset+size is larger than file size" do + @file = open_big_file_fixture + -> { IO::Buffer.map(@file, 17, IO::Buffer::PAGE_SIZE) }.should raise_error(ArgumentError, "Offset too large!") + ensure + # Windows requires the file to be closed before deletion. + @file.close unless @file.closed? + end + end + + it "raises TypeError if offset is not convertible to Integer" do + @file = open_fixture + -> { IO::Buffer.map(@file, 4, "4096") }.should raise_error(TypeError, /no implicit conversion/) + -> { IO::Buffer.map(@file, 4, nil) }.should raise_error(TypeError, /no implicit conversion/) + end + + it "raises a SystemCallError if offset is not an allowed value" do + @file = open_fixture + -> { IO::Buffer.map(@file, 4, 3) }.should raise_error(SystemCallError) + end + + ruby_version_is ""..."4.0" do + it "raises a SystemCallError if offset is negative" do + @file = open_fixture + -> { IO::Buffer.map(@file, 4, -1) }.should raise_error(SystemCallError) + end + end + + ruby_version_is "4.0" do + it "raises ArgumentError if offset is negative" do + @file = open_fixture + -> { IO::Buffer.map(@file, 4, -1) }.should raise_error(ArgumentError, "Offset can't be negative!") + end + end + end + + context "with flags argument" do + context "when READONLY flag is specified" do + it "sets readonly flag on the buffer, allowing only reads" do + @file = open_fixture + @buffer = IO::Buffer.map(@file, nil, 0, IO::Buffer::READONLY) + + @buffer.should.readonly? + + @buffer.get_string.should == "abc\xC3\xA2def\n".b + end + + it "allows mapping read-only files" do + @file = File.open("#{__dir__}/../fixtures/read_text.txt", "r") + @buffer = IO::Buffer.map(@file, nil, 0, IO::Buffer::READONLY) + + @buffer.should.readonly? + + @buffer.get_string.should == "abc\xC3\xA2def\n".b + end + + it "causes IO::Buffer::AccessError on write" do + @file = open_fixture + @buffer = IO::Buffer.map(@file, nil, 0, IO::Buffer::READONLY) + + -> { @buffer.set_string("test") }.should raise_error(IO::Buffer::AccessError, "Buffer is not writable!") + end + end + + context "when PRIVATE is specified" do + it "sets private flag on the buffer, making it freely modifiable" do + @file = open_fixture + @buffer = IO::Buffer.map(@file, nil, 0, IO::Buffer::PRIVATE) + + @buffer.should.private? + @buffer.should_not.shared? + @buffer.should_not.external? + + @buffer.get_string.should == "abc\xC3\xA2def\n".b + @buffer.set_string("test12345") + @buffer.get_string.should == "test12345".b + + @file.read.should == "abcâdef\n" + end + + it "allows mapping read-only files and modifying the buffer" do + @file = File.open("#{__dir__}/../fixtures/read_text.txt", "r") + @buffer = IO::Buffer.map(@file, nil, 0, IO::Buffer::PRIVATE) + + @buffer.should.private? + @buffer.should_not.shared? + @buffer.should_not.external? + + @buffer.get_string.should == "abc\xC3\xA2def\n".b + @buffer.set_string("test12345") + @buffer.get_string.should == "test12345".b + + @file.read.should == "abcâdef\n" + end + + platform_is_not :windows do + it "is not shared across processes" do + file_name = tmp("shared_buffer") + @file = File.open(file_name, "w+") + @file << "I'm private" + @file.rewind + @buffer = IO::Buffer.map(@file, nil, 0, IO::Buffer::PRIVATE) + + IO.popen("-") do |child_pipe| + if child_pipe + # Synchronize on child's output. + child_pipe.readlines.first.chomp.should == @buffer.to_s + @buffer.get_string.should == "I'm private" + + @file.read.should == "I'm private" + else + @buffer.set_string("I'm shared!") + puts @buffer + end + ensure + child_pipe&.close + end + ensure + File.unlink(file_name) + end + end + end + end +end diff --git a/spec/ruby/core/io/buffer/mapped_spec.rb b/spec/ruby/core/io/buffer/mapped_spec.rb index b3610207ffb100..13dc548ed26e72 100644 --- a/spec/ruby/core/io/buffer/mapped_spec.rb +++ b/spec/ruby/core/io/buffer/mapped_spec.rb @@ -6,103 +6,18 @@ @buffer = nil end - context "with a buffer created with .new" do - it "is false for an internal buffer" do - @buffer = IO::Buffer.new(4) - @buffer.mapped?.should be_false - end - - it "is true for a mapped buffer" do - @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) - @buffer.mapped?.should be_true - end - end - - context "with a file-backed buffer created with .map" do - it "is true for a regular mapping" do - File.open(__FILE__, "r") do |file| - @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) - @buffer.mapped?.should be_true - end - end - - ruby_version_is "3.3" do - it "is true for a private mapping" do - File.open(__FILE__, "r") do |file| - @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY | IO::Buffer::PRIVATE) - @buffer.mapped?.should be_true - end - end - end - end - - context "with a String-backed buffer created with .for" do - it "is false for a buffer created without a block" do - @buffer = IO::Buffer.for("test") - @buffer.mapped?.should be_false - end - - it "is false for a buffer created with a block" do - IO::Buffer.for(+"test") do |buffer| - buffer.mapped?.should be_false - end - end + it "is true for a buffer with mapped memory" do + @buffer = IO::Buffer.new(12, IO::Buffer::MAPPED) + @buffer.mapped?.should be_true end - ruby_version_is "3.3" do - context "with a String-backed buffer created with .string" do - it "is false" do - IO::Buffer.string(4) do |buffer| - buffer.mapped?.should be_false - end - end - end + it "is false for a buffer with non-mapped memory" do + @buffer = IO::Buffer.for("string") + @buffer.mapped?.should be_false end - # Always false for slices - context "with a slice of a buffer" do - context "created with .new" do - it "is false when slicing an internal buffer" do - @buffer = IO::Buffer.new(4) - @buffer.slice.mapped?.should be_false - end - - it "is false when slicing a mapped buffer" do - @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) - @buffer.slice.mapped?.should be_false - end - end - - context "created with .map" do - it "is false" do - File.open(__FILE__, "r") do |file| - @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) - @buffer.slice.mapped?.should be_false - end - end - end - - context "created with .for" do - it "is false when slicing a buffer created without a block" do - @buffer = IO::Buffer.for("test") - @buffer.slice.mapped?.should be_false - end - - it "is false when slicing a buffer created with a block" do - IO::Buffer.for(+"test") do |buffer| - buffer.slice.mapped?.should be_false - end - end - end - - ruby_version_is "3.3" do - context "created with .string" do - it "is false" do - IO::Buffer.string(4) do |buffer| - buffer.slice.mapped?.should be_false - end - end - end - end + it "is false for a null buffer" do + @buffer = IO::Buffer.new(0) + @buffer.mapped?.should be_false end end diff --git a/spec/ruby/core/io/buffer/null_spec.rb b/spec/ruby/core/io/buffer/null_spec.rb index 3fb1144d0ed66f..3a0e7f841bf94d 100644 --- a/spec/ruby/core/io/buffer/null_spec.rb +++ b/spec/ruby/core/io/buffer/null_spec.rb @@ -14,11 +14,9 @@ @buffer.null?.should be_false end - ruby_version_is "3.3" do - it "is false for a 0-length String-backed buffer created with .string" do - IO::Buffer.string(0) do |buffer| - buffer.null?.should be_false - end + it "is false for a 0-length String-backed buffer created with .string" do + IO::Buffer.string(0) do |buffer| + buffer.null?.should be_false end end diff --git a/spec/ruby/core/io/buffer/private_spec.rb b/spec/ruby/core/io/buffer/private_spec.rb index 7aa308997b1939..86b7a7a0d0b391 100644 --- a/spec/ruby/core/io/buffer/private_spec.rb +++ b/spec/ruby/core/io/buffer/private_spec.rb @@ -1,111 +1,23 @@ require_relative '../../../spec_helper' -ruby_version_is "3.3" do - describe "IO::Buffer#private?" do - after :each do - @buffer&.free - @buffer = nil - end - - context "with a buffer created with .new" do - it "is false for an internal buffer" do - @buffer = IO::Buffer.new(4, IO::Buffer::INTERNAL) - @buffer.private?.should be_false - end - - it "is false for a mapped buffer" do - @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) - @buffer.private?.should be_false - end - end - - context "with a file-backed buffer created with .map" do - it "is false for a regular mapping" do - File.open(__FILE__, "r") do |file| - @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) - @buffer.private?.should be_false - end - end - - it "is true for a private mapping" do - File.open(__FILE__, "r") do |file| - @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY | IO::Buffer::PRIVATE) - @buffer.private?.should be_true - end - end - end - - context "with a String-backed buffer created with .for" do - it "is false for a buffer created without a block" do - @buffer = IO::Buffer.for("test") - @buffer.private?.should be_false - end - - it "is false for a buffer created with a block" do - IO::Buffer.for(+"test") do |buffer| - buffer.private?.should be_false - end - end - end - - context "with a String-backed buffer created with .string" do - it "is false" do - IO::Buffer.string(4) do |buffer| - buffer.private?.should be_false - end - end - end - - # Always false for slices - context "with a slice of a buffer" do - context "created with .new" do - it "is false when slicing an internal buffer" do - @buffer = IO::Buffer.new(4) - @buffer.slice.private?.should be_false - end - - it "is false when slicing a mapped buffer" do - @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) - @buffer.slice.private?.should be_false - end - end - - context "created with .map" do - it "is false when slicing a regular file-backed buffer" do - File.open(__FILE__, "r") do |file| - @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) - @buffer.slice.private?.should be_false - end - end - - it "is false when slicing a private file-backed buffer" do - File.open(__FILE__, "r") do |file| - @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY | IO::Buffer::PRIVATE) - @buffer.slice.private?.should be_false - end - end - end +describe "IO::Buffer#private?" do + after :each do + @buffer&.free + @buffer = nil + end - context "created with .for" do - it "is false when slicing a buffer created without a block" do - @buffer = IO::Buffer.for("test") - @buffer.slice.private?.should be_false - end + it "is true for a buffer created with PRIVATE flag" do + @buffer = IO::Buffer.new(12, IO::Buffer::INTERNAL | IO::Buffer::PRIVATE) + @buffer.private?.should be_true + end - it "is false when slicing a buffer created with a block" do - IO::Buffer.for(+"test") do |buffer| - buffer.slice.private?.should be_false - end - end - end + it "is false for a buffer created without PRIVATE flag" do + @buffer = IO::Buffer.new(12, IO::Buffer::INTERNAL) + @buffer.private?.should be_false + end - context "created with .string" do - it "is false" do - IO::Buffer.string(4) do |buffer| - buffer.slice.private?.should be_false - end - end - end - end + it "is false for a null buffer" do + @buffer = IO::Buffer.new(0) + @buffer.private?.should be_false end end diff --git a/spec/ruby/core/io/buffer/readonly_spec.rb b/spec/ruby/core/io/buffer/readonly_spec.rb index 0014a876ed743e..2fc7d340b77b80 100644 --- a/spec/ruby/core/io/buffer/readonly_spec.rb +++ b/spec/ruby/core/io/buffer/readonly_spec.rb @@ -6,138 +6,23 @@ @buffer = nil end - context "with a buffer created with .new" do - it "is false for an internal buffer" do - @buffer = IO::Buffer.new(4, IO::Buffer::INTERNAL) - @buffer.readonly?.should be_false - end - - it "is false for a mapped buffer" do - @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) - @buffer.readonly?.should be_false - end - end - - context "with a file-backed buffer created with .map" do - it "is false for a writable mapping" do - File.open(__FILE__, "r+") do |file| - @buffer = IO::Buffer.map(file) - @buffer.readonly?.should be_false - end - end - - it "is true for a readonly mapping" do - File.open(__FILE__, "r") do |file| - @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) - @buffer.readonly?.should be_true - end - end - - ruby_version_is "3.3" do - it "is false for a private mapping" do - File.open(__FILE__, "r") do |file| - @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::PRIVATE) - @buffer.readonly?.should be_false - end - end - end + it "is true for a buffer created with READONLY flag" do + @buffer = IO::Buffer.new(12, IO::Buffer::INTERNAL | IO::Buffer::READONLY) + @buffer.readonly?.should be_true end - context "with a String-backed buffer created with .for" do - it "is true for a buffer created without a block" do - @buffer = IO::Buffer.for(+"test") - @buffer.readonly?.should be_true - end - - it "is false for a buffer created with a block" do - IO::Buffer.for(+"test") do |buffer| - buffer.readonly?.should be_false - end - end - - it "is true for a buffer created with a block from a frozen string" do - IO::Buffer.for(-"test") do |buffer| - buffer.readonly?.should be_true - end - end + it "is true for a buffer that is non-writable" do + @buffer = IO::Buffer.for("string") + @buffer.readonly?.should be_true end - ruby_version_is "3.3" do - context "with a String-backed buffer created with .string" do - it "is false" do - IO::Buffer.string(4) do |buffer| - buffer.readonly?.should be_false - end - end - end + it "is false for a modifiable buffer" do + @buffer = IO::Buffer.new(12) + @buffer.readonly?.should be_false end - # This seems to be the only flag propagated from the source buffer to the slice. - context "with a slice of a buffer" do - context "created with .new" do - it "is false when slicing an internal buffer" do - @buffer = IO::Buffer.new(4) - @buffer.slice.readonly?.should be_false - end - - it "is false when slicing a mapped buffer" do - @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) - @buffer.slice.readonly?.should be_false - end - end - - context "created with .map" do - it "is false when slicing a read-write file-backed buffer" do - File.open(__FILE__, "r+") do |file| - @buffer = IO::Buffer.map(file) - @buffer.slice.readonly?.should be_false - end - end - - it "is true when slicing a readonly file-backed buffer" do - File.open(__FILE__, "r") do |file| - @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) - @buffer.slice.readonly?.should be_true - end - end - - ruby_version_is "3.3" do - it "is false when slicing a private file-backed buffer" do - File.open(__FILE__, "r") do |file| - @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::PRIVATE) - @buffer.slice.readonly?.should be_false - end - end - end - end - - context "created with .for" do - it "is true when slicing a buffer created without a block" do - @buffer = IO::Buffer.for(+"test") - @buffer.slice.readonly?.should be_true - end - - it "is false when slicing a buffer created with a block" do - IO::Buffer.for(+"test") do |buffer| - buffer.slice.readonly?.should be_false - end - end - - it "is true when slicing a buffer created with a block from a frozen string" do - IO::Buffer.for(-"test") do |buffer| - buffer.slice.readonly?.should be_true - end - end - end - - ruby_version_is "3.3" do - context "created with .string" do - it "is false" do - IO::Buffer.string(4) do |buffer| - buffer.slice.readonly?.should be_false - end - end - end - end + it "is false for a null buffer" do + @buffer = IO::Buffer.new(0) + @buffer.readonly?.should be_false end end diff --git a/spec/ruby/core/io/buffer/resize_spec.rb b/spec/ruby/core/io/buffer/resize_spec.rb index 0da3a23356c0dc..a5e80439dac653 100644 --- a/spec/ruby/core/io/buffer/resize_spec.rb +++ b/spec/ruby/core/io/buffer/resize_spec.rb @@ -44,17 +44,15 @@ end end - ruby_version_is "3.3" do - it "resizes private buffer, discarding excess contents" do - File.open(__FILE__, "r") do |file| - @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::PRIVATE) - @buffer.resize(10) - @buffer.size.should == 10 - @buffer.get_string.should == "require_re" - @buffer.resize(12) - @buffer.size.should == 12 - @buffer.get_string.should == "require_re\0\0" - end + it "resizes private buffer, discarding excess contents" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::PRIVATE) + @buffer.resize(10) + @buffer.size.should == 10 + @buffer.get_string.should == "require_re" + @buffer.resize(12) + @buffer.size.should == 12 + @buffer.get_string.should == "require_re\0\0" end end end @@ -76,12 +74,10 @@ end end - ruby_version_is "3.3" do - context "with a String-backed buffer created with .string" do - it "disallows resizing, raising IO::Buffer::AccessError" do - IO::Buffer.string(4) do |buffer| - -> { buffer.resize(10) }.should raise_error(IO::Buffer::AccessError, "Cannot resize external buffer!") - end + context "with a String-backed buffer created with .string" do + it "disallows resizing, raising IO::Buffer::AccessError" do + IO::Buffer.string(4) do |buffer| + -> { buffer.resize(10) }.should raise_error(IO::Buffer::AccessError, "Cannot resize external buffer!") end end end diff --git a/spec/ruby/core/io/buffer/shared/null_and_empty.rb b/spec/ruby/core/io/buffer/shared/null_and_empty.rb index c8fe9e5e46ca9b..2ff5cf8f410db7 100644 --- a/spec/ruby/core/io/buffer/shared/null_and_empty.rb +++ b/spec/ruby/core/io/buffer/shared/null_and_empty.rb @@ -21,11 +21,9 @@ @buffer.send(@method).should be_false end - ruby_version_is "3.3" do - it "is false for a non-empty String-backed buffer created with .string" do - IO::Buffer.string(4) do |buffer| - buffer.send(@method).should be_false - end + it "is false for a non-empty String-backed buffer created with .string" do + IO::Buffer.string(4) do |buffer| + buffer.send(@method).should be_false end end diff --git a/spec/ruby/core/io/buffer/shared_spec.rb b/spec/ruby/core/io/buffer/shared_spec.rb index f2a638cf39f9b1..4f3bce5448fee0 100644 --- a/spec/ruby/core/io/buffer/shared_spec.rb +++ b/spec/ruby/core/io/buffer/shared_spec.rb @@ -6,112 +6,25 @@ @buffer = nil end - context "with a buffer created with .new" do - it "is false for an internal buffer" do - @buffer = IO::Buffer.new(4, IO::Buffer::INTERNAL) - @buffer.shared?.should be_false - end - - it "is false for a mapped buffer" do - @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) - @buffer.shared?.should be_false - end - end - - context "with a file-backed buffer created with .map" do - it "is true for a regular mapping" do - File.open(__FILE__, "r") do |file| - @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) - @buffer.shared?.should be_true - end - end - - ruby_version_is "3.3" do - it "is false for a private mapping" do - File.open(__FILE__, "r") do |file| - @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY | IO::Buffer::PRIVATE) - @buffer.shared?.should be_false - end - end - end + it "is true for a buffer created with SHARED flag" do + @buffer = IO::Buffer.new(12, IO::Buffer::INTERNAL | IO::Buffer::SHARED) + @buffer.shared?.should be_true end - context "with a String-backed buffer created with .for" do - it "is false for a buffer created without a block" do - @buffer = IO::Buffer.for("test") - @buffer.shared?.should be_false - end - - it "is false for a buffer created with a block" do - IO::Buffer.for(+"test") do |buffer| - buffer.shared?.should be_false - end - end + it "is true for a non-private buffer created with .map" do + file = File.open("#{__dir__}/../fixtures/read_text.txt", "r+") + @buffer = IO::Buffer.map(file) + file.close + @buffer.shared?.should be_true end - ruby_version_is "3.3" do - context "with a String-backed buffer created with .string" do - it "is false" do - IO::Buffer.string(4) do |buffer| - buffer.shared?.should be_false - end - end - end + it "is false for an unshared buffer" do + @buffer = IO::Buffer.new(12) + @buffer.shared?.should be_false end - # Always false for slices - context "with a slice of a buffer" do - context "created with .new" do - it "is false when slicing an internal buffer" do - @buffer = IO::Buffer.new(4) - @buffer.slice.shared?.should be_false - end - - it "is false when slicing a mapped buffer" do - @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) - @buffer.slice.shared?.should be_false - end - end - - context "created with .map" do - it "is false when slicing a regular file-backed buffer" do - File.open(__FILE__, "r") do |file| - @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) - @buffer.slice.shared?.should be_false - end - end - - ruby_version_is "3.3" do - it "is false when slicing a private file-backed buffer" do - File.open(__FILE__, "r") do |file| - @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY | IO::Buffer::PRIVATE) - @buffer.slice.shared?.should be_false - end - end - end - end - - context "created with .for" do - it "is false when slicing a buffer created without a block" do - @buffer = IO::Buffer.for("test") - @buffer.slice.shared?.should be_false - end - - it "is false when slicing a buffer created with a block" do - IO::Buffer.for(+"test") do |buffer| - buffer.slice.shared?.should be_false - end - end - end - - ruby_version_is "3.3" do - context "created with .string" do - it "is false" do - IO::Buffer.string(4) do |buffer| - buffer.slice.shared?.should be_false - end - end - end - end + it "is false for a null buffer" do + @buffer = IO::Buffer.new(0) + @buffer.shared?.should be_false end end diff --git a/spec/ruby/core/io/buffer/string_spec.rb b/spec/ruby/core/io/buffer/string_spec.rb new file mode 100644 index 00000000000000..bc7a73075e3948 --- /dev/null +++ b/spec/ruby/core/io/buffer/string_spec.rb @@ -0,0 +1,62 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer.string" do + it "creates a modifiable buffer for the duration of the block" do + IO::Buffer.string(7) do |buffer| + @buffer = buffer + + buffer.size.should == 7 + buffer.get_string.should == "\0\0\0\0\0\0\0".b + + buffer.set_string("test") + buffer.get_string.should == "test\0\0\0" + end + @buffer.should.null? + end + + it "returns contents of the buffer as a binary string" do + string = + IO::Buffer.string(7) do |buffer| + buffer.set_string("ä test") + end + string.should == "\xC3\xA4 test".b + end + + it "creates an external buffer" do + IO::Buffer.string(8) do |buffer| + buffer.should_not.internal? + buffer.should_not.mapped? + buffer.should.external? + + buffer.should_not.empty? + buffer.should_not.null? + + buffer.should_not.shared? + buffer.should_not.private? + buffer.should_not.readonly? + + buffer.should_not.locked? + buffer.should.valid? + end + end + + it "returns an empty string if size is 0" do + string = + IO::Buffer.string(0) do |buffer| + buffer.size.should == 0 + end + string.should == "" + end + + it "raises ArgumentError if size is negative" do + -> { IO::Buffer.string(-1) {} }.should raise_error(ArgumentError, "negative string size (or size too big)") + end + + it "raises RangeError if size is too large" do + -> { IO::Buffer.string(2 ** 232) {} }.should raise_error(RangeError, /\Abignum too big to convert into [`']long'\z/) + end + + it "raises LocalJumpError if no block is given" do + -> { IO::Buffer.string(7) }.should raise_error(LocalJumpError, "no block given") + end +end diff --git a/spec/ruby/core/io/buffer/transfer_spec.rb b/spec/ruby/core/io/buffer/transfer_spec.rb index cb8c843ff24750..5b7b63e3339991 100644 --- a/spec/ruby/core/io/buffer/transfer_spec.rb +++ b/spec/ruby/core/io/buffer/transfer_spec.rb @@ -60,17 +60,15 @@ end end - ruby_version_is "3.3" do - context "with a String-backed buffer created with .string" do - it "transfers memory to a new buffer, breaking the transaction by nullifying the original" do - IO::Buffer.string(4) do |buffer| - info = buffer.to_s - @buffer = buffer.transfer - @buffer.to_s.should == info - buffer.null?.should be_true - end - @buffer.null?.should be_false + context "with a String-backed buffer created with .string" do + it "transfers memory to a new buffer, breaking the transaction by nullifying the original" do + IO::Buffer.string(4) do |buffer| + info = buffer.to_s + @buffer = buffer.transfer + @buffer.to_s.should == info + buffer.null?.should be_true end + @buffer.null?.should be_false end end diff --git a/spec/ruby/core/io/foreach_spec.rb b/spec/ruby/core/io/foreach_spec.rb index 6abe8901bac7a0..28d6fef7ae5079 100644 --- a/spec/ruby/core/io/foreach_spec.rb +++ b/spec/ruby/core/io/foreach_spec.rb @@ -47,14 +47,12 @@ end end - ruby_version_is "3.3" do - # https://bugs.ruby-lang.org/issues/19630 - it "warns about deprecation given a path with a pipe" do - cmd = "|echo ok" - -> { - IO.foreach(cmd).to_a - }.should complain(/IO process creation with a leading '\|'/) - end + # https://bugs.ruby-lang.org/issues/19630 + it "warns about deprecation given a path with a pipe" do + cmd = "|echo ok" + -> { + IO.foreach(cmd).to_a + }.should complain(/IO process creation with a leading '\|'/) end end end diff --git a/spec/ruby/core/io/gets_spec.rb b/spec/ruby/core/io/gets_spec.rb index ca64bf860e4148..0587fa07c43289 100644 --- a/spec/ruby/core/io/gets_spec.rb +++ b/spec/ruby/core/io/gets_spec.rb @@ -338,23 +338,11 @@ @io.gets.encoding.should == Encoding::BINARY end - ruby_version_is ''...'3.3' do - it "transcodes to internal encoding if the IO object's external encoding is BINARY" do - Encoding.default_external = Encoding::BINARY - Encoding.default_internal = Encoding::UTF_8 - @io = new_io @name, 'r' - @io.set_encoding Encoding::BINARY, Encoding::UTF_8 - @io.gets.encoding.should == Encoding::UTF_8 - end - end - - ruby_version_is '3.3' do - it "ignores the internal encoding if the IO object's external encoding is BINARY" do - Encoding.default_external = Encoding::BINARY - Encoding.default_internal = Encoding::UTF_8 - @io = new_io @name, 'r' - @io.set_encoding Encoding::BINARY, Encoding::UTF_8 - @io.gets.encoding.should == Encoding::BINARY - end + it "ignores the internal encoding if the IO object's external encoding is BINARY" do + Encoding.default_external = Encoding::BINARY + Encoding.default_internal = Encoding::UTF_8 + @io = new_io @name, 'r' + @io.set_encoding Encoding::BINARY, Encoding::UTF_8 + @io.gets.encoding.should == Encoding::BINARY end end diff --git a/spec/ruby/core/io/pread_spec.rb b/spec/ruby/core/io/pread_spec.rb index dc7bcedf3e5c2f..8f7d9b2521d9c9 100644 --- a/spec/ruby/core/io/pread_spec.rb +++ b/spec/ruby/core/io/pread_spec.rb @@ -1,140 +1,138 @@ # -*- encoding: utf-8 -*- require_relative '../../spec_helper' -guard -> { platform_is_not :windows or ruby_version_is "3.3" } do - describe "IO#pread" do - before :each do - @fname = tmp("io_pread.txt") - @contents = "1234567890" - touch(@fname) { |f| f.write @contents } - @file = File.open(@fname, "r+") - end - - after :each do - @file.close - rm_r @fname - end +describe "IO#pread" do + before :each do + @fname = tmp("io_pread.txt") + @contents = "1234567890" + touch(@fname) { |f| f.write @contents } + @file = File.open(@fname, "r+") + end - it "accepts a length, and an offset" do - @file.pread(4, 0).should == "1234" - @file.pread(3, 4).should == "567" - end + after :each do + @file.close + rm_r @fname + end - it "accepts a length, an offset, and an output buffer" do - buffer = +"foo" - @file.pread(3, 4, buffer).should.equal?(buffer) - buffer.should == "567" - end + it "accepts a length, and an offset" do + @file.pread(4, 0).should == "1234" + @file.pread(3, 4).should == "567" + end - it "shrinks the buffer in case of less bytes read" do - buffer = +"foo" - @file.pread(1, 0, buffer) - buffer.should == "1" - end + it "accepts a length, an offset, and an output buffer" do + buffer = +"foo" + @file.pread(3, 4, buffer).should.equal?(buffer) + buffer.should == "567" + end - it "grows the buffer in case of more bytes read" do - buffer = +"foo" - @file.pread(5, 0, buffer) - buffer.should == "12345" - end + it "shrinks the buffer in case of less bytes read" do + buffer = +"foo" + @file.pread(1, 0, buffer) + buffer.should == "1" + end - it "preserves the encoding of the given buffer" do - buffer = ''.encode(Encoding::ISO_8859_1) - @file.pread(10, 0, buffer) + it "grows the buffer in case of more bytes read" do + buffer = +"foo" + @file.pread(5, 0, buffer) + buffer.should == "12345" + end - buffer.encoding.should == Encoding::ISO_8859_1 - end + it "preserves the encoding of the given buffer" do + buffer = ''.encode(Encoding::ISO_8859_1) + @file.pread(10, 0, buffer) - it "does not advance the file pointer" do - @file.pread(4, 0).should == "1234" - @file.read.should == "1234567890" - end + buffer.encoding.should == Encoding::ISO_8859_1 + end - it "ignores the current offset" do - @file.pos = 3 - @file.pread(4, 0).should == "1234" - end + it "does not advance the file pointer" do + @file.pread(4, 0).should == "1234" + @file.read.should == "1234567890" + end - it "returns an empty string for maxlen = 0" do - @file.pread(0, 4).should == "" - end + it "ignores the current offset" do + @file.pos = 3 + @file.pread(4, 0).should == "1234" + end - it "returns a buffer for maxlen = 0 when buffer specified" do - buffer = +"foo" - @file.pread(0, 4, buffer).should.equal?(buffer) - buffer.should == "foo" - end + it "returns an empty string for maxlen = 0" do + @file.pread(0, 4).should == "" + end - it "ignores the offset for maxlen = 0, even if it is out of file bounds" do - @file.pread(0, 400).should == "" - end + it "returns a buffer for maxlen = 0 when buffer specified" do + buffer = +"foo" + @file.pread(0, 4, buffer).should.equal?(buffer) + buffer.should == "foo" + end - it "does not reset the buffer when reading with maxlen = 0" do - buffer = +"foo" - @file.pread(0, 4, buffer) - buffer.should == "foo" + it "ignores the offset for maxlen = 0, even if it is out of file bounds" do + @file.pread(0, 400).should == "" + end - @file.pread(0, 400, buffer) - buffer.should == "foo" - end + it "does not reset the buffer when reading with maxlen = 0" do + buffer = +"foo" + @file.pread(0, 4, buffer) + buffer.should == "foo" - it "converts maxlen to Integer using #to_int" do - maxlen = mock('maxlen') - maxlen.should_receive(:to_int).and_return(4) - @file.pread(maxlen, 0).should == "1234" - end + @file.pread(0, 400, buffer) + buffer.should == "foo" + end - it "converts offset to Integer using #to_int" do - offset = mock('offset') - offset.should_receive(:to_int).and_return(0) - @file.pread(4, offset).should == "1234" - end + it "converts maxlen to Integer using #to_int" do + maxlen = mock('maxlen') + maxlen.should_receive(:to_int).and_return(4) + @file.pread(maxlen, 0).should == "1234" + end - it "converts a buffer to String using to_str" do - buffer = mock('buffer') - buffer.should_receive(:to_str).at_least(1).and_return(+"foo") - @file.pread(4, 0, buffer) - buffer.should_not.is_a?(String) - buffer.to_str.should == "1234" - end + it "converts offset to Integer using #to_int" do + offset = mock('offset') + offset.should_receive(:to_int).and_return(0) + @file.pread(4, offset).should == "1234" + end - it "raises TypeError if maxlen is not an Integer and cannot be coerced into Integer" do - maxlen = Object.new - -> { @file.pread(maxlen, 0) }.should raise_error(TypeError, 'no implicit conversion of Object into Integer') - end + it "converts a buffer to String using to_str" do + buffer = mock('buffer') + buffer.should_receive(:to_str).at_least(1).and_return(+"foo") + @file.pread(4, 0, buffer) + buffer.should_not.is_a?(String) + buffer.to_str.should == "1234" + end - it "raises TypeError if offset is not an Integer and cannot be coerced into Integer" do - offset = Object.new - -> { @file.pread(4, offset) }.should raise_error(TypeError, 'no implicit conversion of Object into Integer') - end + it "raises TypeError if maxlen is not an Integer and cannot be coerced into Integer" do + maxlen = Object.new + -> { @file.pread(maxlen, 0) }.should raise_error(TypeError, 'no implicit conversion of Object into Integer') + end - it "raises ArgumentError for negative values of maxlen" do - -> { @file.pread(-4, 0) }.should raise_error(ArgumentError, 'negative string size (or size too big)') - end + it "raises TypeError if offset is not an Integer and cannot be coerced into Integer" do + offset = Object.new + -> { @file.pread(4, offset) }.should raise_error(TypeError, 'no implicit conversion of Object into Integer') + end - it "raised Errno::EINVAL for negative values of offset" do - -> { @file.pread(4, -1) }.should raise_error(Errno::EINVAL, /Invalid argument/) - end + it "raises ArgumentError for negative values of maxlen" do + -> { @file.pread(-4, 0) }.should raise_error(ArgumentError, 'negative string size (or size too big)') + end - it "raises TypeError if the buffer is not a String and cannot be coerced into String" do - buffer = Object.new - -> { @file.pread(4, 0, buffer) }.should raise_error(TypeError, 'no implicit conversion of Object into String') - end + it "raised Errno::EINVAL for negative values of offset" do + -> { @file.pread(4, -1) }.should raise_error(Errno::EINVAL, /Invalid argument/) + end - it "raises EOFError if end-of-file is reached" do - -> { @file.pread(1, 10) }.should raise_error(EOFError) - end + it "raises TypeError if the buffer is not a String and cannot be coerced into String" do + buffer = Object.new + -> { @file.pread(4, 0, buffer) }.should raise_error(TypeError, 'no implicit conversion of Object into String') + end - it "raises IOError when file is not open in read mode" do - File.open(@fname, "w") do |file| - -> { file.pread(1, 1) }.should raise_error(IOError) - end - end + it "raises EOFError if end-of-file is reached" do + -> { @file.pread(1, 10) }.should raise_error(EOFError) + end - it "raises IOError when file is closed" do - file = File.open(@fname, "r+") - file.close + it "raises IOError when file is not open in read mode" do + File.open(@fname, "w") do |file| -> { file.pread(1, 1) }.should raise_error(IOError) end end + + it "raises IOError when file is closed" do + file = File.open(@fname, "r+") + file.close + -> { file.pread(1, 1) }.should raise_error(IOError) + end end diff --git a/spec/ruby/core/io/pwrite_spec.rb b/spec/ruby/core/io/pwrite_spec.rb index 2bc508b37d1660..fd0b6cf380c463 100644 --- a/spec/ruby/core/io/pwrite_spec.rb +++ b/spec/ruby/core/io/pwrite_spec.rb @@ -1,69 +1,67 @@ # -*- encoding: utf-8 -*- require_relative '../../spec_helper' -guard -> { platform_is_not :windows or ruby_version_is "3.3" } do - describe "IO#pwrite" do - before :each do - @fname = tmp("io_pwrite.txt") - @file = File.open(@fname, "w+") - end +describe "IO#pwrite" do + before :each do + @fname = tmp("io_pwrite.txt") + @file = File.open(@fname, "w+") + end - after :each do - @file.close - rm_r @fname - end + after :each do + @file.close + rm_r @fname + end - it "returns the number of bytes written" do - @file.pwrite("foo", 0).should == 3 - end + it "returns the number of bytes written" do + @file.pwrite("foo", 0).should == 3 + end - it "accepts a string and an offset" do - @file.pwrite("foo", 2) - @file.pread(3, 2).should == "foo" - end + it "accepts a string and an offset" do + @file.pwrite("foo", 2) + @file.pread(3, 2).should == "foo" + end - it "does not advance the pointer in the file" do - @file.pwrite("bar", 3) - @file.write("foo") - @file.pread(6, 0).should == "foobar" - end + it "does not advance the pointer in the file" do + @file.pwrite("bar", 3) + @file.write("foo") + @file.pread(6, 0).should == "foobar" + end - it "calls #to_s on the object to be written" do - object = mock("to_s") - object.should_receive(:to_s).and_return("foo") - @file.pwrite(object, 0) - @file.pread(3, 0).should == "foo" - end + it "calls #to_s on the object to be written" do + object = mock("to_s") + object.should_receive(:to_s).and_return("foo") + @file.pwrite(object, 0) + @file.pread(3, 0).should == "foo" + end - it "calls #to_int on the offset" do - offset = mock("to_int") - offset.should_receive(:to_int).and_return(2) - @file.pwrite("foo", offset) - @file.pread(3, 2).should == "foo" - end + it "calls #to_int on the offset" do + offset = mock("to_int") + offset.should_receive(:to_int).and_return(2) + @file.pwrite("foo", offset) + @file.pread(3, 2).should == "foo" + end - it "raises IOError when file is not open in write mode" do - File.open(@fname, "r") do |file| - -> { file.pwrite("foo", 1) }.should raise_error(IOError, "not opened for writing") - end + it "raises IOError when file is not open in write mode" do + File.open(@fname, "r") do |file| + -> { file.pwrite("foo", 1) }.should raise_error(IOError, "not opened for writing") end + end - it "raises IOError when file is closed" do - file = File.open(@fname, "w+") - file.close - -> { file.pwrite("foo", 1) }.should raise_error(IOError, "closed stream") - end + it "raises IOError when file is closed" do + file = File.open(@fname, "w+") + file.close + -> { file.pwrite("foo", 1) }.should raise_error(IOError, "closed stream") + end - it "raises a NoMethodError if object does not respond to #to_s" do - -> { - @file.pwrite(BasicObject.new, 0) - }.should raise_error(NoMethodError, /undefined method [`']to_s'/) - end + it "raises a NoMethodError if object does not respond to #to_s" do + -> { + @file.pwrite(BasicObject.new, 0) + }.should raise_error(NoMethodError, /undefined method [`']to_s'/) + end - it "raises a TypeError if the offset cannot be converted to an Integer" do - -> { - @file.pwrite("foo", Object.new) - }.should raise_error(TypeError, "no implicit conversion of Object into Integer") - end + it "raises a TypeError if the offset cannot be converted to an Integer" do + -> { + @file.pwrite("foo", Object.new) + }.should raise_error(TypeError, "no implicit conversion of Object into Integer") end end diff --git a/spec/ruby/core/io/read_spec.rb b/spec/ruby/core/io/read_spec.rb index 988ec2ce30df25..dfb42e09db7681 100644 --- a/spec/ruby/core/io/read_spec.rb +++ b/spec/ruby/core/io/read_spec.rb @@ -65,15 +65,6 @@ end platform_is_not :windows do - ruby_version_is ""..."3.3" do - it "uses an :open_args option" do - string = IO.read(@fname, nil, 0, open_args: ["r", nil, {encoding: Encoding::US_ASCII}]) - string.encoding.should == Encoding::US_ASCII - - string = IO.read(@fname, nil, 0, open_args: ["r", nil, {}]) - string.encoding.should == Encoding::UTF_8 - end - end end it "disregards other options if :open_args is given" do @@ -135,18 +126,9 @@ -> { IO.read @fname, -1 }.should raise_error(ArgumentError) end - ruby_version_is ''...'3.3' do - it "raises an Errno::EINVAL when not passed a valid offset" do - -> { IO.read @fname, 0, -1 }.should raise_error(Errno::EINVAL) - -> { IO.read @fname, -1, -1 }.should raise_error(Errno::EINVAL) - end - end - - ruby_version_is '3.3' do - it "raises an ArgumentError when not passed a valid offset" do - -> { IO.read @fname, 0, -1 }.should raise_error(ArgumentError) - -> { IO.read @fname, -1, -1 }.should raise_error(ArgumentError) - end + it "raises an ArgumentError when not passed a valid offset" do + -> { IO.read @fname, 0, -1 }.should raise_error(ArgumentError) + -> { IO.read @fname, -1, -1 }.should raise_error(ArgumentError) end it "uses the external encoding specified via the :external_encoding option" do @@ -232,14 +214,12 @@ end end - ruby_version_is "3.3" do - # https://bugs.ruby-lang.org/issues/19630 - it "warns about deprecation" do - cmd = "|echo ok" - -> { - IO.read(cmd) - }.should complain(/IO process creation with a leading '\|'/) - end + # https://bugs.ruby-lang.org/issues/19630 + it "warns about deprecation" do + cmd = "|echo ok" + -> { + IO.read(cmd) + }.should complain(/IO process creation with a leading '\|'/) end end end @@ -322,11 +302,9 @@ -> { @io.read(nil, 'frozen-string'.freeze) }.should raise_error(FrozenError) end - ruby_bug "", ""..."3.3" do - it "raise FrozenError if the output buffer is frozen (2)" do - @io.read - -> { @io.read(1, ''.freeze) }.should raise_error(FrozenError) - end + it "raise FrozenError if the output buffer is frozen (2)" do + @io.read + -> { @io.read(1, ''.freeze) }.should raise_error(FrozenError) end it "consumes zero bytes when reading zero bytes" do diff --git a/spec/ruby/core/io/readlines_spec.rb b/spec/ruby/core/io/readlines_spec.rb index b4770775d1e813..07d29ea5317f2d 100644 --- a/spec/ruby/core/io/readlines_spec.rb +++ b/spec/ruby/core/io/readlines_spec.rb @@ -207,14 +207,12 @@ end end - ruby_version_is "3.3" do - # https://bugs.ruby-lang.org/issues/19630 - it "warns about deprecation given a path with a pipe" do - cmd = "|echo ok" - -> { - IO.readlines(cmd) - }.should complain(/IO process creation with a leading '\|'/) - end + # https://bugs.ruby-lang.org/issues/19630 + it "warns about deprecation given a path with a pipe" do + cmd = "|echo ok" + -> { + IO.readlines(cmd) + }.should complain(/IO process creation with a leading '\|'/) end end diff --git a/spec/ruby/core/io/select_spec.rb b/spec/ruby/core/io/select_spec.rb index 3893e7620f9e10..9fdb7e12c932db 100644 --- a/spec/ruby/core/io/select_spec.rb +++ b/spec/ruby/core/io/select_spec.rb @@ -149,16 +149,28 @@ end end -describe "IO.select when passed nil for timeout" do - it "sleeps forever and sets the thread status to 'sleep'" do - t = Thread.new do - IO.select(nil, nil, nil, nil) +describe "IO.select with infinite timeout" do + describe :io_select_infinite_timeout, shared: true do + it "sleeps forever and sets the thread status to 'sleep'" do + t = Thread.new do + IO.select(nil, nil, nil, @method) + end + + Thread.pass while t.status && t.status != "sleep" + t.join unless t.status + t.status.should == "sleep" + t.kill + t.join end + end - Thread.pass while t.status && t.status != "sleep" - t.join unless t.status - t.status.should == "sleep" - t.kill - t.join + describe "IO.select when passed nil for timeout" do + it_behaves_like :io_select_infinite_timeout, nil + end + + ruby_version_is "4.0" do + describe "IO.select when passed Float::INFINITY for timeout" do + it_behaves_like :io_select_infinite_timeout, Float::INFINITY + end end end diff --git a/spec/ruby/core/io/shared/readlines.rb b/spec/ruby/core/io/shared/readlines.rb index 6c1fa11a596800..77eb9cbd65cb8f 100644 --- a/spec/ruby/core/io/shared/readlines.rb +++ b/spec/ruby/core/io/shared/readlines.rb @@ -83,11 +83,9 @@ -> { IO.send(@method, @name, 2**128, &@object) }.should raise_error(RangeError) end - ruby_bug "#18767", ""..."3.3" do - describe "when passed limit" do - it "raises ArgumentError when passed 0 as a limit" do - -> { IO.send(@method, @name, 0, &@object) }.should raise_error(ArgumentError) - end + describe "when passed limit" do + it "raises ArgumentError when passed 0 as a limit" do + -> { IO.send(@method, @name, 0, &@object) }.should raise_error(ArgumentError) end end end diff --git a/spec/ruby/core/io/write_spec.rb b/spec/ruby/core/io/write_spec.rb index e58100f8467d9c..95e6371985bf7c 100644 --- a/spec/ruby/core/io/write_spec.rb +++ b/spec/ruby/core/io/write_spec.rb @@ -102,6 +102,13 @@ File.binread(@filename).should == "h\u0000\u0000\u0000i\u0000\u0000\u0000" end + it "ignores the 'bom|' prefix" do + File.open(@filename, "w", encoding: 'bom|utf-8') do |file| + file.write("hi") + end + File.binread(@filename).should == "hi" + end + it "raises a invalid byte sequence error if invalid bytes are being written" do # pack "\xFEhi" to avoid utf-8 conflict xFEhi = ([254].pack('C*') + 'hi').force_encoding('utf-8') @@ -220,7 +227,7 @@ end end - ruby_version_is "3.3"..."4.0" do + ruby_version_is ""..."4.0" do # https://bugs.ruby-lang.org/issues/19630 it "warns about deprecation given a path with a pipe" do -> { diff --git a/spec/ruby/core/kernel/Integer_spec.rb b/spec/ruby/core/kernel/Integer_spec.rb index 74dd3e0dd2ef27..c62b8b08013898 100644 --- a/spec/ruby/core/kernel/Integer_spec.rb +++ b/spec/ruby/core/kernel/Integer_spec.rb @@ -586,19 +586,10 @@ Integer("777", obj).should == 0777 end - # https://bugs.ruby-lang.org/issues/19349 - ruby_version_is ''...'3.3' do - it "ignores the base if it is not an integer and does not respond to #to_i" do - Integer("777", "8").should == 777 - end - end - - ruby_version_is '3.3' do - it "raises a TypeError if it is not an integer and does not respond to #to_i" do - -> { - Integer("777", "8") - }.should raise_error(TypeError, "no implicit conversion of String into Integer") - end + it "raises a TypeError if it is not an integer and does not respond to #to_i" do + -> { + Integer("777", "8") + }.should raise_error(TypeError, "no implicit conversion of String into Integer") end describe "when passed exception: false" do diff --git a/spec/ruby/core/kernel/caller_spec.rb b/spec/ruby/core/kernel/caller_spec.rb index 7cd703de5a3a8d..df051ef07f2d16 100644 --- a/spec/ruby/core/kernel/caller_spec.rb +++ b/spec/ruby/core/kernel/caller_spec.rb @@ -84,14 +84,25 @@ end guard -> { Kernel.instance_method(:tap).source_location } do - ruby_version_is ""..."4.0" do + ruby_version_is ""..."3.4" do it "includes core library methods defined in Ruby" do file, line = Kernel.instance_method(:tap).source_location file.should.start_with?(' { lambda(&proc{}) }.should complain("#{__FILE__}:#{__LINE__}: warning: lambda without a literal block is deprecated; use the proc without lambda instead\n") - end - end - - ruby_version_is "3.3" do - it "raises when proc isn't a lambda" do - -> { lambda(&proc{}) }.should raise_error(ArgumentError, /the lambda method requires a literal block/) - end + it "raises when proc isn't a lambda" do + -> { lambda(&proc{}) }.should raise_error(ArgumentError, /the lambda method requires a literal block/) end it "doesn't warn when proc is lambda" do diff --git a/spec/ruby/core/kernel/open_spec.rb b/spec/ruby/core/kernel/open_spec.rb index b967d5044ba92b..9d3f3760b96b3e 100644 --- a/spec/ruby/core/kernel/open_spec.rb +++ b/spec/ruby/core/kernel/open_spec.rb @@ -79,14 +79,12 @@ end end - ruby_version_is "3.3" do - # https://bugs.ruby-lang.org/issues/19630 - it "warns about deprecation given a path with a pipe" do - cmd = "|echo ok" - -> { - open(cmd) { |f| f.read } - }.should complain(/Kernel#open with a leading '\|'/) - end + # https://bugs.ruby-lang.org/issues/19630 + it "warns about deprecation given a path with a pipe" do + cmd = "|echo ok" + -> { + open(cmd) { |f| f.read } + }.should complain(/Kernel#open with a leading '\|'/) end end diff --git a/spec/ruby/core/kernel/shared/require.rb b/spec/ruby/core/kernel/shared/require.rb index 52f86f73e50f48..ef5b9486c6157d 100644 --- a/spec/ruby/core/kernel/shared/require.rb +++ b/spec/ruby/core/kernel/shared/require.rb @@ -266,15 +266,13 @@ ScratchPad.recorded.should == [:loaded] end - ruby_bug "#17340", ''...'3.3' do - it "loads a file concurrently" do - path = File.expand_path "concurrent_require_fixture.rb", CODE_LOADING_DIR - ScratchPad.record(@object) - -> { - @object.require(path) - }.should_not complain(/circular require considered harmful/, verbose: true) - ScratchPad.recorded.join - end + it "loads a file concurrently" do + path = File.expand_path "concurrent_require_fixture.rb", CODE_LOADING_DIR + ScratchPad.record(@object) + -> { + @object.require(path) + }.should_not complain(/circular require considered harmful/, verbose: true) + ScratchPad.recorded.join end end diff --git a/spec/ruby/core/kernel/sleep_spec.rb b/spec/ruby/core/kernel/sleep_spec.rb index e9c600aac41107..0b003ad189a48b 100644 --- a/spec/ruby/core/kernel/sleep_spec.rb +++ b/spec/ruby/core/kernel/sleep_spec.rb @@ -63,27 +63,19 @@ def o.divmod(*); [0, 0.001]; end actual_duration.should > 0.01 # 100 * 0.0001 => 0.01 end - ruby_version_is ""..."3.3" do - it "raises a TypeError when passed nil" do - -> { sleep(nil) }.should raise_error(TypeError) + it "accepts a nil duration" do + running = false + t = Thread.new do + running = true + sleep(nil) + 5 end - end - - ruby_version_is "3.3" do - it "accepts a nil duration" do - running = false - t = Thread.new do - running = true - sleep(nil) - 5 - end - Thread.pass until running - Thread.pass while t.status and t.status != "sleep" + Thread.pass until running + Thread.pass while t.status and t.status != "sleep" - t.wakeup - t.value.should == 5 - end + t.wakeup + t.value.should == 5 end context "Kernel.sleep with Fiber scheduler" do diff --git a/spec/ruby/core/marshal/shared/load.rb b/spec/ruby/core/marshal/shared/load.rb index 204a4d34e3edff..692c14cfa10adb 100644 --- a/spec/ruby/core/marshal/shared/load.rb +++ b/spec/ruby/core/marshal/shared/load.rb @@ -127,36 +127,32 @@ Object.should_not.frozen? end - ruby_bug "#19427", ""..."3.3" do - it "does freeze extended objects" do - object = Marshal.load("\x04\be:\x0FEnumerableo:\vObject\x00", freeze: true) - object.should.frozen? - end + it "does freeze extended objects" do + object = Marshal.load("\x04\be:\x0FEnumerableo:\vObject\x00", freeze: true) + object.should.frozen? + end - it "does freeze extended objects with instance variables" do - object = Marshal.load("\x04\be:\x0FEnumerableo:\vObject\x06:\n@ivarT", freeze: true) - object.should.frozen? - end + it "does freeze extended objects with instance variables" do + object = Marshal.load("\x04\be:\x0FEnumerableo:\vObject\x06:\n@ivarT", freeze: true) + object.should.frozen? end - ruby_bug "#19427", ""..."3.3" do - it "returns frozen object having #_dump method" do - object = Marshal.send(@method, Marshal.dump(UserDefined.new), freeze: true) - object.should.frozen? - end + it "returns frozen object having #_dump method" do + object = Marshal.send(@method, Marshal.dump(UserDefined.new), freeze: true) + object.should.frozen? + end - it "returns frozen object responding to #marshal_dump and #marshal_load" do - object = Marshal.send(@method, Marshal.dump(UserMarshal.new), freeze: true) - object.should.frozen? - end + it "returns frozen object responding to #marshal_dump and #marshal_load" do + object = Marshal.send(@method, Marshal.dump(UserMarshal.new), freeze: true) + object.should.frozen? + end - it "returns frozen object extended by a module" do - object = Object.new - object.extend(MarshalSpec::ModuleToExtendBy) + it "returns frozen object extended by a module" do + object = Object.new + object.extend(MarshalSpec::ModuleToExtendBy) - object = Marshal.send(@method, Marshal.dump(object), freeze: true) - object.should.frozen? - end + object = Marshal.send(@method, Marshal.dump(object), freeze: true) + object.should.frozen? end it "does not call freeze method" do @@ -239,12 +235,10 @@ string.should.frozen? end - ruby_bug "#19427", ""..."3.3" do - it "call the proc with extended objects" do - objs = [] - obj = Marshal.load("\x04\be:\x0FEnumerableo:\vObject\x00", Proc.new { |o| objs << o; o }) - objs.should == [obj] - end + it "call the proc with extended objects" do + objs = [] + obj = Marshal.load("\x04\be:\x0FEnumerableo:\vObject\x00", Proc.new { |o| objs << o; o }) + objs.should == [obj] end it "returns the value of the proc" do @@ -930,15 +924,13 @@ def io.binmode; raise "binmode"; end [Meths, UserRegexp, Regexp] end - ruby_bug "#19439", ""..."3.3" do - it "restore the regexp instance variables" do - obj = Regexp.new("hello") - obj.instance_variable_set(:@regexp_ivar, [42]) + it "restore the regexp instance variables" do + obj = Regexp.new("hello") + obj.instance_variable_set(:@regexp_ivar, [42]) - new_obj = Marshal.send(@method, "\x04\bI/\nhello\x00\a:\x06EF:\x11@regexp_ivar[\x06i/") - new_obj.instance_variables.should == [:@regexp_ivar] - new_obj.instance_variable_get(:@regexp_ivar).should == [42] - end + new_obj = Marshal.send(@method, "\x04\bI/\nhello\x00\a:\x06EF:\x11@regexp_ivar[\x06i/") + new_obj.instance_variables.should == [:@regexp_ivar] + new_obj.instance_variable_get(:@regexp_ivar).should == [42] end it "preserves Regexp encoding" do diff --git a/spec/ruby/core/matchdata/named_captures_spec.rb b/spec/ruby/core/matchdata/named_captures_spec.rb index 5e4693d62d662b..10b1f884d6e4c9 100644 --- a/spec/ruby/core/matchdata/named_captures_spec.rb +++ b/spec/ruby/core/matchdata/named_captures_spec.rb @@ -13,15 +13,13 @@ /\A(?.)(?.)(?.)(?.)?\z/.match('012').named_captures.should == { 'a' => '0', 'b' => '2' } end - ruby_version_is "3.3" do - it 'returns a Hash with Symbol keys when symbolize_names is provided a true value' do - /(?.)(?.)?/.match('0').named_captures(symbolize_names: true).should == { a: '0', b: nil } - /(?.)(?.)?/.match('0').named_captures(symbolize_names: "truly").should == { a: '0', b: nil } - end + it 'returns a Hash with Symbol keys when symbolize_names is provided a true value' do + /(?.)(?.)?/.match('0').named_captures(symbolize_names: true).should == { a: '0', b: nil } + /(?.)(?.)?/.match('0').named_captures(symbolize_names: "truly").should == { a: '0', b: nil } + end - it 'returns a Hash with String keys when symbolize_names is provided a false value' do - /(?.)(?.)?/.match('02').named_captures(symbolize_names: false).should == { 'a' => '0', 'b' => '2' } - /(?.)(?.)?/.match('02').named_captures(symbolize_names: nil).should == { 'a' => '0', 'b' => '2' } - end + it 'returns a Hash with String keys when symbolize_names is provided a false value' do + /(?.)(?.)?/.match('02').named_captures(symbolize_names: false).should == { 'a' => '0', 'b' => '2' } + /(?.)(?.)?/.match('02').named_captures(symbolize_names: nil).should == { 'a' => '0', 'b' => '2' } end end diff --git a/spec/ruby/core/math/log10_spec.rb b/spec/ruby/core/math/log10_spec.rb index c4daedcd5c9d75..f3bd7fd4b86474 100644 --- a/spec/ruby/core/math/log10_spec.rb +++ b/spec/ruby/core/math/log10_spec.rb @@ -23,6 +23,10 @@ -> { Math.log10("test") }.should raise_error(TypeError) end + it "raises a TypeError if passed a numerical argument as a string" do + -> { Math.log10("1.0") }.should raise_error(TypeError) + end + it "returns NaN given NaN" do Math.log10(nan_value).nan?.should be_true end diff --git a/spec/ruby/core/module/set_temporary_name_spec.rb b/spec/ruby/core/module/set_temporary_name_spec.rb index 46605ed6758877..0b96b869c90c51 100644 --- a/spec/ruby/core/module/set_temporary_name_spec.rb +++ b/spec/ruby/core/module/set_temporary_name_spec.rb @@ -1,147 +1,145 @@ require_relative '../../spec_helper' require_relative 'fixtures/set_temporary_name' -ruby_version_is "3.3" do - describe "Module#set_temporary_name" do - it "can assign a temporary name" do - m = Module.new - m.name.should be_nil +describe "Module#set_temporary_name" do + it "can assign a temporary name" do + m = Module.new + m.name.should be_nil - m.set_temporary_name("fake_name") - m.name.should == "fake_name" + m.set_temporary_name("fake_name") + m.name.should == "fake_name" - m.set_temporary_name(nil) - m.name.should be_nil - end + m.set_temporary_name(nil) + m.name.should be_nil + end - it "returns self" do - m = Module.new - m.set_temporary_name("fake_name").should.equal? m - end + it "returns self" do + m = Module.new + m.set_temporary_name("fake_name").should.equal? m + end - it "can assign a temporary name which is not a valid constant path" do - m = Module.new + it "can assign a temporary name which is not a valid constant path" do + m = Module.new - m.set_temporary_name("name") - m.name.should == "name" + m.set_temporary_name("name") + m.name.should == "name" - m.set_temporary_name("Template['foo.rb']") - m.name.should == "Template['foo.rb']" + m.set_temporary_name("Template['foo.rb']") + m.name.should == "Template['foo.rb']" - m.set_temporary_name("a::B") - m.name.should == "a::B" + m.set_temporary_name("a::B") + m.name.should == "a::B" - m.set_temporary_name("A::b") - m.name.should == "A::b" + m.set_temporary_name("A::b") + m.name.should == "A::b" - m.set_temporary_name("A::B::") - m.name.should == "A::B::" + m.set_temporary_name("A::B::") + m.name.should == "A::B::" - m.set_temporary_name("A::::B") - m.name.should == "A::::B" + m.set_temporary_name("A::::B") + m.name.should == "A::::B" - m.set_temporary_name("A=") - m.name.should == "A=" - end + m.set_temporary_name("A=") + m.name.should == "A=" + end - it "can't assign empty string as name" do - m = Module.new - -> { m.set_temporary_name("") }.should raise_error(ArgumentError, "empty class/module name") - end + it "can't assign empty string as name" do + m = Module.new + -> { m.set_temporary_name("") }.should raise_error(ArgumentError, "empty class/module name") + end - it "can't assign a constant name as a temporary name" do - m = Module.new - -> { m.set_temporary_name("Object") }.should raise_error(ArgumentError, "the temporary name must not be a constant path to avoid confusion") - end + it "can't assign a constant name as a temporary name" do + m = Module.new + -> { m.set_temporary_name("Object") }.should raise_error(ArgumentError, "the temporary name must not be a constant path to avoid confusion") + end - it "can't assign a constant path as a temporary name" do - m = Module.new - -> { m.set_temporary_name("A::B") }.should raise_error(ArgumentError, "the temporary name must not be a constant path to avoid confusion") - -> { m.set_temporary_name("::A") }.should raise_error(ArgumentError, "the temporary name must not be a constant path to avoid confusion") - -> { m.set_temporary_name("::A::B") }.should raise_error(ArgumentError, "the temporary name must not be a constant path to avoid confusion") - end + it "can't assign a constant path as a temporary name" do + m = Module.new + -> { m.set_temporary_name("A::B") }.should raise_error(ArgumentError, "the temporary name must not be a constant path to avoid confusion") + -> { m.set_temporary_name("::A") }.should raise_error(ArgumentError, "the temporary name must not be a constant path to avoid confusion") + -> { m.set_temporary_name("::A::B") }.should raise_error(ArgumentError, "the temporary name must not be a constant path to avoid confusion") + end - it "can't assign name to permanent module" do - -> { Object.set_temporary_name("fake_name") }.should raise_error(RuntimeError, "can't change permanent name") - end + it "can't assign name to permanent module" do + -> { Object.set_temporary_name("fake_name") }.should raise_error(RuntimeError, "can't change permanent name") + end - it "can assign a temporary name to a module nested into an anonymous module" do - m = Module.new - module m::N; end - m::N.name.should =~ /\A#::N\z/ + it "can assign a temporary name to a module nested into an anonymous module" do + m = Module.new + module m::N; end + m::N.name.should =~ /\A#::N\z/ - m::N.set_temporary_name("fake_name") - m::N.name.should == "fake_name" + m::N.set_temporary_name("fake_name") + m::N.name.should == "fake_name" - m::N.set_temporary_name(nil) - m::N.name.should be_nil - end + m::N.set_temporary_name(nil) + m::N.name.should be_nil + end - it "discards a temporary name when an outer anonymous module gets a permanent name" do - m = Module.new - module m::N; end + it "discards a temporary name when an outer anonymous module gets a permanent name" do + m = Module.new + module m::N; end - m::N.set_temporary_name("fake_name") - m::N.name.should == "fake_name" + m::N.set_temporary_name("fake_name") + m::N.name.should == "fake_name" - ModuleSpecs::SetTemporaryNameSpec::M = m - m::N.name.should == "ModuleSpecs::SetTemporaryNameSpec::M::N" - ModuleSpecs::SetTemporaryNameSpec.send :remove_const, :M - end + ModuleSpecs::SetTemporaryNameSpec::M = m + m::N.name.should == "ModuleSpecs::SetTemporaryNameSpec::M::N" + ModuleSpecs::SetTemporaryNameSpec.send :remove_const, :M + end - it "can update the name when assigned to a constant" do - m = Module.new - m::N = Module.new - m::N.name.should =~ /\A#::N\z/ - m::N.set_temporary_name(nil) + it "can update the name when assigned to a constant" do + m = Module.new + m::N = Module.new + m::N.name.should =~ /\A#::N\z/ + m::N.set_temporary_name(nil) - m::M = m::N - m::M.name.should =~ /\A#::M\z/m - end + m::M = m::N + m::M.name.should =~ /\A#::M\z/m + end - it "can reassign a temporary name repeatedly" do - m = Module.new + it "can reassign a temporary name repeatedly" do + m = Module.new - m.set_temporary_name("fake_name") - m.name.should == "fake_name" + m.set_temporary_name("fake_name") + m.name.should == "fake_name" - m.set_temporary_name("fake_name_2") - m.name.should == "fake_name_2" - end + m.set_temporary_name("fake_name_2") + m.name.should == "fake_name_2" + end - ruby_bug "#21094", ""..."4.0" do - it "also updates a name of a nested module" do - m = Module.new - m::N = Module.new - m::N.name.should =~ /\A#::N\z/ + ruby_bug "#21094", ""..."4.0" do + it "also updates a name of a nested module" do + m = Module.new + m::N = Module.new + m::N.name.should =~ /\A#::N\z/ - m.set_temporary_name "m" - m::N.name.should == "m::N" + m.set_temporary_name "m" + m::N.name.should == "m::N" - m.set_temporary_name nil - m::N.name.should == nil - end + m.set_temporary_name nil + m::N.name.should == nil end + end - it "keeps temporary name when assigned in an anonymous module" do - outer = Module.new - m = Module.new - m.set_temporary_name "m" - m.name.should == "m" - outer::M = m - m.name.should == "m" - m.inspect.should == "m" - end + it "keeps temporary name when assigned in an anonymous module" do + outer = Module.new + m = Module.new + m.set_temporary_name "m" + m.name.should == "m" + outer::M = m + m.name.should == "m" + m.inspect.should == "m" + end - it "keeps temporary name when assigned in an anonymous module and nested before" do - outer = Module.new - m = Module.new - outer::A = m - m.set_temporary_name "m" - m.name.should == "m" - outer::M = m - m.name.should == "m" - m.inspect.should == "m" - end + it "keeps temporary name when assigned in an anonymous module and nested before" do + outer = Module.new + m = Module.new + outer::A = m + m.set_temporary_name "m" + m.name.should == "m" + outer::M = m + m.name.should == "m" + m.inspect.should == "m" end end diff --git a/spec/ruby/core/module/shared/class_eval.rb b/spec/ruby/core/module/shared/class_eval.rb index b1d5cb3814edea..526d0a20363dc8 100644 --- a/spec/ruby/core/module/shared/class_eval.rb +++ b/spec/ruby/core/module/shared/class_eval.rb @@ -52,10 +52,8 @@ def foo ModuleSpecs.send(@method, "[__FILE__, __LINE__]", "test", 102).should == ["test", 102] end - ruby_version_is "3.3" do - it "uses the caller location as default filename" do - ModuleSpecs.send(@method, "[__FILE__, __LINE__]").should == ["(eval at #{__FILE__}:#{__LINE__})", 1] - end + it "uses the caller location as default filename" do + ModuleSpecs.send(@method, "[__FILE__, __LINE__]").should == ["(eval at #{__FILE__}:#{__LINE__})", 1] end it "converts a non-string filename to a string using to_str" do diff --git a/spec/ruby/core/nil/singleton_method_spec.rb b/spec/ruby/core/nil/singleton_method_spec.rb index 8d898b1cc94d70..fb47af0c3e8c5d 100644 --- a/spec/ruby/core/nil/singleton_method_spec.rb +++ b/spec/ruby/core/nil/singleton_method_spec.rb @@ -1,15 +1,13 @@ require_relative '../../spec_helper' describe "NilClass#singleton_method" do - ruby_version_is '3.3' do - it "raises regardless of whether NilClass defines the method" do + it "raises regardless of whether NilClass defines the method" do + -> { nil.singleton_method(:foo) }.should raise_error(NameError) + begin + def (nil).foo; end -> { nil.singleton_method(:foo) }.should raise_error(NameError) - begin - def (nil).foo; end - -> { nil.singleton_method(:foo) }.should raise_error(NameError) - ensure - NilClass.send(:remove_method, :foo) - end + ensure + NilClass.send(:remove_method, :foo) end end end diff --git a/spec/ruby/core/numeric/remainder_spec.rb b/spec/ruby/core/numeric/remainder_spec.rb index 674fa22d8ef997..29654310d231e2 100644 --- a/spec/ruby/core/numeric/remainder_spec.rb +++ b/spec/ruby/core/numeric/remainder_spec.rb @@ -6,9 +6,7 @@ @obj = NumericSpecs::Subclass.new @result = mock("Numeric#% result") @other = mock("Passed Object") - ruby_version_is "3.3" do - @other.should_receive(:coerce).with(@obj).and_return([@obj, @other]) - end + @other.should_receive(:coerce).with(@obj).and_return([@obj, @other]) end it "returns the result of calling self#% with other if self is 0" do diff --git a/spec/ruby/core/objectspace/weakkeymap/clear_spec.rb b/spec/ruby/core/objectspace/weakkeymap/clear_spec.rb index 8050e2c30729d6..b1804ec9b003b7 100644 --- a/spec/ruby/core/objectspace/weakkeymap/clear_spec.rb +++ b/spec/ruby/core/objectspace/weakkeymap/clear_spec.rb @@ -1,27 +1,25 @@ require_relative '../../../spec_helper' -ruby_version_is '3.3' do - describe "ObjectSpace::WeakKeyMap#clear" do - it "removes all the entries" do - m = ObjectSpace::WeakKeyMap.new +describe "ObjectSpace::WeakKeyMap#clear" do + it "removes all the entries" do + m = ObjectSpace::WeakKeyMap.new - key = Object.new - value = Object.new - m[key] = value + key = Object.new + value = Object.new + m[key] = value - key2 = Object.new - value2 = Object.new - m[key2] = value2 + key2 = Object.new + value2 = Object.new + m[key2] = value2 - m.clear + m.clear - m.key?(key).should == false - m.key?(key2).should == false - end + m.key?(key).should == false + m.key?(key2).should == false + end - it "returns self" do - m = ObjectSpace::WeakKeyMap.new - m.clear.should.equal?(m) - end + it "returns self" do + m = ObjectSpace::WeakKeyMap.new + m.clear.should.equal?(m) end end diff --git a/spec/ruby/core/objectspace/weakkeymap/delete_spec.rb b/spec/ruby/core/objectspace/weakkeymap/delete_spec.rb index 3cd61355d64f9f..ad32c2c75efda4 100644 --- a/spec/ruby/core/objectspace/weakkeymap/delete_spec.rb +++ b/spec/ruby/core/objectspace/weakkeymap/delete_spec.rb @@ -1,51 +1,49 @@ require_relative '../../../spec_helper' -ruby_version_is '3.3' do - describe "ObjectSpace::WeakKeyMap#delete" do - it "removes the entry and returns the deleted value" do - m = ObjectSpace::WeakKeyMap.new - key = Object.new - value = Object.new - m[key] = value - - m.delete(key).should == value - m.key?(key).should == false - end +describe "ObjectSpace::WeakKeyMap#delete" do + it "removes the entry and returns the deleted value" do + m = ObjectSpace::WeakKeyMap.new + key = Object.new + value = Object.new + m[key] = value + + m.delete(key).should == value + m.key?(key).should == false + end - it "uses equality semantic" do - m = ObjectSpace::WeakKeyMap.new - key = "foo".upcase - value = Object.new - m[key] = value + it "uses equality semantic" do + m = ObjectSpace::WeakKeyMap.new + key = "foo".upcase + value = Object.new + m[key] = value - m.delete("foo".upcase).should == value - m.key?(key).should == false - end + m.delete("foo".upcase).should == value + m.key?(key).should == false + end - it "calls supplied block if the key is not found" do - key = Object.new - m = ObjectSpace::WeakKeyMap.new - return_value = m.delete(key) do |yielded_key| - yielded_key.should == key - 5 - end - return_value.should == 5 + it "calls supplied block if the key is not found" do + key = Object.new + m = ObjectSpace::WeakKeyMap.new + return_value = m.delete(key) do |yielded_key| + yielded_key.should == key + 5 end + return_value.should == 5 + end - it "returns nil if the key is not found when no block is given" do - m = ObjectSpace::WeakKeyMap.new - m.delete(Object.new).should == nil - end + it "returns nil if the key is not found when no block is given" do + m = ObjectSpace::WeakKeyMap.new + m.delete(Object.new).should == nil + end - it "returns nil when a key cannot be garbage collected" do - map = ObjectSpace::WeakKeyMap.new + it "returns nil when a key cannot be garbage collected" do + map = ObjectSpace::WeakKeyMap.new - map.delete(1).should == nil - map.delete(1.0).should == nil - map.delete(:a).should == nil - map.delete(true).should == nil - map.delete(false).should == nil - map.delete(nil).should == nil - end + map.delete(1).should == nil + map.delete(1.0).should == nil + map.delete(:a).should == nil + map.delete(true).should == nil + map.delete(false).should == nil + map.delete(nil).should == nil end end diff --git a/spec/ruby/core/objectspace/weakkeymap/element_reference_spec.rb b/spec/ruby/core/objectspace/weakkeymap/element_reference_spec.rb index 51368e8d3ba3af..53eff79c40fb1c 100644 --- a/spec/ruby/core/objectspace/weakkeymap/element_reference_spec.rb +++ b/spec/ruby/core/objectspace/weakkeymap/element_reference_spec.rb @@ -1,107 +1,105 @@ require_relative '../../../spec_helper' require_relative 'fixtures/classes' -ruby_version_is "3.3" do - describe "ObjectSpace::WeakKeyMap#[]" do - it "is faithful to the map's content" do - map = ObjectSpace::WeakKeyMap.new - key1, key2 = %w[a b].map(&:upcase) - ref1, ref2 = %w[x y] - map[key1] = ref1 - map[key1].should == ref1 - map[key1] = ref1 - map[key1].should == ref1 - map[key2] = ref2 - map[key1].should == ref1 - map[key2].should == ref2 - end - - it "compares keys with #eql? semantics" do - map = ObjectSpace::WeakKeyMap.new - key = [1.0] - map[key] = "x" - map[[1]].should == nil - map[[1.0]].should == "x" - key.should == [1.0] # keep the key alive until here to keep the map entry - - map = ObjectSpace::WeakKeyMap.new - key = [1] - map[key] = "x" - map[[1.0]].should == nil - map[[1]].should == "x" - key.should == [1] # keep the key alive until here to keep the map entry - - map = ObjectSpace::WeakKeyMap.new - key1, key2 = %w[a a].map(&:upcase) - ref = "x" - map[key1] = ref - map[key2].should == ref - end - - it "compares key via #hash first" do - x = mock('0') - x.should_receive(:hash).and_return(0) - - map = ObjectSpace::WeakKeyMap.new - key = 'foo' - map[key] = :bar - map[x].should == nil - end - - it "does not compare keys with different #hash values via #eql?" do - x = mock('x') - x.should_not_receive(:eql?) - x.stub!(:hash).and_return(0) - - y = mock('y') - y.should_not_receive(:eql?) - y.stub!(:hash).and_return(1) - - map = ObjectSpace::WeakKeyMap.new - map[y] = 1 - map[x].should == nil - end - - it "compares keys with the same #hash value via #eql?" do - x = mock('x') - x.should_receive(:eql?).and_return(true) - x.stub!(:hash).and_return(42) - - y = mock('y') - y.should_not_receive(:eql?) - y.stub!(:hash).and_return(42) - - map = ObjectSpace::WeakKeyMap.new - map[y] = 1 - map[x].should == 1 - end - - it "finds a value via an identical key even when its #eql? isn't reflexive" do - x = mock('x') - x.should_receive(:hash).at_least(1).and_return(42) - x.stub!(:eql?).and_return(false) # Stubbed for clarity and latitude in implementation; not actually sent by MRI. - - map = ObjectSpace::WeakKeyMap.new - map[x] = :x - map[x].should == :x - end - - it "supports keys with private #hash method" do - key = WeakKeyMapSpecs::KeyWithPrivateHash.new - map = ObjectSpace::WeakKeyMap.new - map[key] = 42 - map[key].should == 42 - end - - it "returns nil and does not raise error when a key cannot be garbage collected" do - map = ObjectSpace::WeakKeyMap.new - - map[1].should == nil - map[1.0].should == nil - map[:a].should == nil - map[true].should == nil - map[false].should == nil - map[nil].should == nil - end +describe "ObjectSpace::WeakKeyMap#[]" do + it "is faithful to the map's content" do + map = ObjectSpace::WeakKeyMap.new + key1, key2 = %w[a b].map(&:upcase) + ref1, ref2 = %w[x y] + map[key1] = ref1 + map[key1].should == ref1 + map[key1] = ref1 + map[key1].should == ref1 + map[key2] = ref2 + map[key1].should == ref1 + map[key2].should == ref2 + end + + it "compares keys with #eql? semantics" do + map = ObjectSpace::WeakKeyMap.new + key = [1.0] + map[key] = "x" + map[[1]].should == nil + map[[1.0]].should == "x" + key.should == [1.0] # keep the key alive until here to keep the map entry + + map = ObjectSpace::WeakKeyMap.new + key = [1] + map[key] = "x" + map[[1.0]].should == nil + map[[1]].should == "x" + key.should == [1] # keep the key alive until here to keep the map entry + + map = ObjectSpace::WeakKeyMap.new + key1, key2 = %w[a a].map(&:upcase) + ref = "x" + map[key1] = ref + map[key2].should == ref + end + + it "compares key via #hash first" do + x = mock('0') + x.should_receive(:hash).and_return(0) + + map = ObjectSpace::WeakKeyMap.new + key = 'foo' + map[key] = :bar + map[x].should == nil + end + + it "does not compare keys with different #hash values via #eql?" do + x = mock('x') + x.should_not_receive(:eql?) + x.stub!(:hash).and_return(0) + + y = mock('y') + y.should_not_receive(:eql?) + y.stub!(:hash).and_return(1) + + map = ObjectSpace::WeakKeyMap.new + map[y] = 1 + map[x].should == nil + end + + it "compares keys with the same #hash value via #eql?" do + x = mock('x') + x.should_receive(:eql?).and_return(true) + x.stub!(:hash).and_return(42) + + y = mock('y') + y.should_not_receive(:eql?) + y.stub!(:hash).and_return(42) + + map = ObjectSpace::WeakKeyMap.new + map[y] = 1 + map[x].should == 1 + end + + it "finds a value via an identical key even when its #eql? isn't reflexive" do + x = mock('x') + x.should_receive(:hash).at_least(1).and_return(42) + x.stub!(:eql?).and_return(false) # Stubbed for clarity and latitude in implementation; not actually sent by MRI. + + map = ObjectSpace::WeakKeyMap.new + map[x] = :x + map[x].should == :x + end + + it "supports keys with private #hash method" do + key = WeakKeyMapSpecs::KeyWithPrivateHash.new + map = ObjectSpace::WeakKeyMap.new + map[key] = 42 + map[key].should == 42 + end + + it "returns nil and does not raise error when a key cannot be garbage collected" do + map = ObjectSpace::WeakKeyMap.new + + map[1].should == nil + map[1.0].should == nil + map[:a].should == nil + map[true].should == nil + map[false].should == nil + map[nil].should == nil end end diff --git a/spec/ruby/core/objectspace/weakkeymap/element_set_spec.rb b/spec/ruby/core/objectspace/weakkeymap/element_set_spec.rb index 8db8d780c71a86..c480aa661ae2fd 100644 --- a/spec/ruby/core/objectspace/weakkeymap/element_set_spec.rb +++ b/spec/ruby/core/objectspace/weakkeymap/element_set_spec.rb @@ -1,82 +1,80 @@ require_relative '../../../spec_helper' -ruby_version_is "3.3" do - describe "ObjectSpace::WeakKeyMap#[]=" do - def should_accept(map, key, value) - (map[key] = value).should == value - map.should.key?(key) - map[key].should == value - end +describe "ObjectSpace::WeakKeyMap#[]=" do + def should_accept(map, key, value) + (map[key] = value).should == value + map.should.key?(key) + map[key].should == value + end + + it "is correct" do + map = ObjectSpace::WeakKeyMap.new + key1, key2 = %w[a b].map(&:upcase) + ref1, ref2 = %w[x y] + should_accept(map, key1, ref1) + should_accept(map, key1, ref1) + should_accept(map, key2, ref2) + map[key1].should == ref1 + end + + it "requires the keys to implement #hash" do + map = ObjectSpace::WeakKeyMap.new + -> { map[BasicObject.new] = 1 }.should raise_error(NoMethodError, /undefined method [`']hash' for an instance of BasicObject/) + end - it "is correct" do + it "accepts frozen keys or values" do + map = ObjectSpace::WeakKeyMap.new + x = Object.new + should_accept(map, x, true) + should_accept(map, x, false) + should_accept(map, x, 42) + should_accept(map, x, :foo) + + y = Object.new.freeze + should_accept(map, x, y) + should_accept(map, y, x) + end + + it "does not duplicate and freeze String keys (like Hash#[]= does)" do + map = ObjectSpace::WeakKeyMap.new + key = +"a" + map[key] = 1 + + map.getkey("a").should.equal? key + map.getkey("a").should_not.frozen? + + key.should == "a" # keep the key alive until here to keep the map entry + end + + context "a key cannot be garbage collected" do + it "raises ArgumentError when Integer is used as a key" do map = ObjectSpace::WeakKeyMap.new - key1, key2 = %w[a b].map(&:upcase) - ref1, ref2 = %w[x y] - should_accept(map, key1, ref1) - should_accept(map, key1, ref1) - should_accept(map, key2, ref2) - map[key1].should == ref1 + -> { map[1] = "x" }.should raise_error(ArgumentError, /WeakKeyMap (keys )?must be garbage collectable/) end - it "requires the keys to implement #hash" do + it "raises ArgumentError when Float is used as a key" do map = ObjectSpace::WeakKeyMap.new - -> { map[BasicObject.new] = 1 }.should raise_error(NoMethodError, /undefined method [`']hash' for an instance of BasicObject/) + -> { map[1.0] = "x" }.should raise_error(ArgumentError, /WeakKeyMap (keys )?must be garbage collectable/) end - it "accepts frozen keys or values" do + it "raises ArgumentError when Symbol is used as a key" do map = ObjectSpace::WeakKeyMap.new - x = Object.new - should_accept(map, x, true) - should_accept(map, x, false) - should_accept(map, x, 42) - should_accept(map, x, :foo) - - y = Object.new.freeze - should_accept(map, x, y) - should_accept(map, y, x) + -> { map[:a] = "x" }.should raise_error(ArgumentError, /WeakKeyMap (keys )?must be garbage collectable/) end - it "does not duplicate and freeze String keys (like Hash#[]= does)" do + it "raises ArgumentError when true is used as a key" do map = ObjectSpace::WeakKeyMap.new - key = +"a" - map[key] = 1 - - map.getkey("a").should.equal? key - map.getkey("a").should_not.frozen? - - key.should == "a" # keep the key alive until here to keep the map entry + -> { map[true] = "x" }.should raise_error(ArgumentError, /WeakKeyMap (keys )?must be garbage collectable/) end - context "a key cannot be garbage collected" do - it "raises ArgumentError when Integer is used as a key" do - map = ObjectSpace::WeakKeyMap.new - -> { map[1] = "x" }.should raise_error(ArgumentError, /WeakKeyMap (keys )?must be garbage collectable/) - end - - it "raises ArgumentError when Float is used as a key" do - map = ObjectSpace::WeakKeyMap.new - -> { map[1.0] = "x" }.should raise_error(ArgumentError, /WeakKeyMap (keys )?must be garbage collectable/) - end - - it "raises ArgumentError when Symbol is used as a key" do - map = ObjectSpace::WeakKeyMap.new - -> { map[:a] = "x" }.should raise_error(ArgumentError, /WeakKeyMap (keys )?must be garbage collectable/) - end - - it "raises ArgumentError when true is used as a key" do - map = ObjectSpace::WeakKeyMap.new - -> { map[true] = "x" }.should raise_error(ArgumentError, /WeakKeyMap (keys )?must be garbage collectable/) - end - - it "raises ArgumentError when false is used as a key" do - map = ObjectSpace::WeakKeyMap.new - -> { map[false] = "x" }.should raise_error(ArgumentError, /WeakKeyMap (keys )?must be garbage collectable/) - end + it "raises ArgumentError when false is used as a key" do + map = ObjectSpace::WeakKeyMap.new + -> { map[false] = "x" }.should raise_error(ArgumentError, /WeakKeyMap (keys )?must be garbage collectable/) + end - it "raises ArgumentError when nil is used as a key" do - map = ObjectSpace::WeakKeyMap.new - -> { map[nil] = "x" }.should raise_error(ArgumentError, /WeakKeyMap (keys )?must be garbage collectable/) - end + it "raises ArgumentError when nil is used as a key" do + map = ObjectSpace::WeakKeyMap.new + -> { map[nil] = "x" }.should raise_error(ArgumentError, /WeakKeyMap (keys )?must be garbage collectable/) end end end diff --git a/spec/ruby/core/objectspace/weakkeymap/getkey_spec.rb b/spec/ruby/core/objectspace/weakkeymap/getkey_spec.rb index 8a2dbf809d8c23..0c8dec8aea5248 100644 --- a/spec/ruby/core/objectspace/weakkeymap/getkey_spec.rb +++ b/spec/ruby/core/objectspace/weakkeymap/getkey_spec.rb @@ -1,28 +1,26 @@ require_relative '../../../spec_helper' -ruby_version_is "3.3" do - describe "ObjectSpace::WeakKeyMap#getkey" do - it "returns the existing equal key" do - map = ObjectSpace::WeakKeyMap.new - key1, key2 = %w[a a].map(&:upcase) +describe "ObjectSpace::WeakKeyMap#getkey" do + it "returns the existing equal key" do + map = ObjectSpace::WeakKeyMap.new + key1, key2 = %w[a a].map(&:upcase) - map[key1] = true - map.getkey(key2).should equal(key1) - map.getkey("X").should == nil + map[key1] = true + map.getkey(key2).should equal(key1) + map.getkey("X").should == nil - key1.should == "A" # keep the key alive until here to keep the map entry - key2.should == "A" # keep the key alive until here to keep the map entry - end + key1.should == "A" # keep the key alive until here to keep the map entry + key2.should == "A" # keep the key alive until here to keep the map entry + end - it "returns nil when a key cannot be garbage collected" do - map = ObjectSpace::WeakKeyMap.new + it "returns nil when a key cannot be garbage collected" do + map = ObjectSpace::WeakKeyMap.new - map.getkey(1).should == nil - map.getkey(1.0).should == nil - map.getkey(:a).should == nil - map.getkey(true).should == nil - map.getkey(false).should == nil - map.getkey(nil).should == nil - end + map.getkey(1).should == nil + map.getkey(1.0).should == nil + map.getkey(:a).should == nil + map.getkey(true).should == nil + map.getkey(false).should == nil + map.getkey(nil).should == nil end end diff --git a/spec/ruby/core/objectspace/weakkeymap/inspect_spec.rb b/spec/ruby/core/objectspace/weakkeymap/inspect_spec.rb index 319f050970e31a..b6bb4691584293 100644 --- a/spec/ruby/core/objectspace/weakkeymap/inspect_spec.rb +++ b/spec/ruby/core/objectspace/weakkeymap/inspect_spec.rb @@ -1,21 +1,19 @@ require_relative '../../../spec_helper' -ruby_version_is "3.3" do - describe "ObjectSpace::WeakKeyMap#inspect" do - it "only displays size in output" do - map = ObjectSpace::WeakKeyMap.new - key1, key2, key3 = "foo", "bar", "bar" - map.inspect.should =~ /\A\#\z/ - map[key1] = 1 - map.inspect.should =~ /\A\#\z/ - map[key2] = 2 - map.inspect.should =~ /\A\#\z/ - map[key3] = 3 - map.inspect.should =~ /\A\#\z/ +describe "ObjectSpace::WeakKeyMap#inspect" do + it "only displays size in output" do + map = ObjectSpace::WeakKeyMap.new + key1, key2, key3 = "foo", "bar", "bar" + map.inspect.should =~ /\A\#\z/ + map[key1] = 1 + map.inspect.should =~ /\A\#\z/ + map[key2] = 2 + map.inspect.should =~ /\A\#\z/ + map[key3] = 3 + map.inspect.should =~ /\A\#\z/ - key1.should == "foo" # keep the key alive until here to keep the map entry - key2.should == "bar" # keep the key alive until here to keep the map entry - key3.should == "bar" # keep the key alive until here to keep the map entry - end + key1.should == "foo" # keep the key alive until here to keep the map entry + key2.should == "bar" # keep the key alive until here to keep the map entry + key3.should == "bar" # keep the key alive until here to keep the map entry end end diff --git a/spec/ruby/core/objectspace/weakkeymap/key_spec.rb b/spec/ruby/core/objectspace/weakkeymap/key_spec.rb index a9a2e12432c845..e0b686667197cc 100644 --- a/spec/ruby/core/objectspace/weakkeymap/key_spec.rb +++ b/spec/ruby/core/objectspace/weakkeymap/key_spec.rb @@ -1,44 +1,42 @@ require_relative '../../../spec_helper' -ruby_version_is "3.3" do - describe "ObjectSpace::WeakKeyMap#key?" do - it "recognizes keys in use" do - map = ObjectSpace::WeakKeyMap.new - key1, key2 = %w[a b].map(&:upcase) - ref1, ref2 = %w[x y] +describe "ObjectSpace::WeakKeyMap#key?" do + it "recognizes keys in use" do + map = ObjectSpace::WeakKeyMap.new + key1, key2 = %w[a b].map(&:upcase) + ref1, ref2 = %w[x y] - map[key1] = ref1 - map.key?(key1).should == true - map[key1] = ref1 - map.key?(key1).should == true - map[key2] = ref2 - map.key?(key2).should == true - end + map[key1] = ref1 + map.key?(key1).should == true + map[key1] = ref1 + map.key?(key1).should == true + map[key2] = ref2 + map.key?(key2).should == true + end - it "matches using equality semantics" do - map = ObjectSpace::WeakKeyMap.new - key1, key2 = %w[a a].map(&:upcase) - ref = "x" - map[key1] = ref - map.key?(key2).should == true - end + it "matches using equality semantics" do + map = ObjectSpace::WeakKeyMap.new + key1, key2 = %w[a a].map(&:upcase) + ref = "x" + map[key1] = ref + map.key?(key2).should == true + end - it "reports true if the pair exists and the value is nil" do - map = ObjectSpace::WeakKeyMap.new - key = Object.new - map[key] = nil - map.key?(key).should == true - end + it "reports true if the pair exists and the value is nil" do + map = ObjectSpace::WeakKeyMap.new + key = Object.new + map[key] = nil + map.key?(key).should == true + end - it "returns false when a key cannot be garbage collected" do - map = ObjectSpace::WeakKeyMap.new + it "returns false when a key cannot be garbage collected" do + map = ObjectSpace::WeakKeyMap.new - map.key?(1).should == false - map.key?(1.0).should == false - map.key?(:a).should == false - map.key?(true).should == false - map.key?(false).should == false - map.key?(nil).should == false - end + map.key?(1).should == false + map.key?(1.0).should == false + map.key?(:a).should == false + map.key?(true).should == false + map.key?(false).should == false + map.key?(nil).should == false end end diff --git a/spec/ruby/core/objectspace/weakmap/delete_spec.rb b/spec/ruby/core/objectspace/weakmap/delete_spec.rb index 302de264fb2998..03beebbb83419d 100644 --- a/spec/ruby/core/objectspace/weakmap/delete_spec.rb +++ b/spec/ruby/core/objectspace/weakmap/delete_spec.rb @@ -1,30 +1,28 @@ require_relative '../../../spec_helper' -ruby_version_is '3.3' do - describe "ObjectSpace::WeakMap#delete" do - it "removes the entry and returns the deleted value" do - m = ObjectSpace::WeakMap.new - key = Object.new - value = Object.new - m[key] = value +describe "ObjectSpace::WeakMap#delete" do + it "removes the entry and returns the deleted value" do + m = ObjectSpace::WeakMap.new + key = Object.new + value = Object.new + m[key] = value - m.delete(key).should == value - m.key?(key).should == false - end + m.delete(key).should == value + m.key?(key).should == false + end - it "calls supplied block if the key is not found" do - key = Object.new - m = ObjectSpace::WeakMap.new - return_value = m.delete(key) do |yielded_key| - yielded_key.should == key - 5 - end - return_value.should == 5 + it "calls supplied block if the key is not found" do + key = Object.new + m = ObjectSpace::WeakMap.new + return_value = m.delete(key) do |yielded_key| + yielded_key.should == key + 5 end + return_value.should == 5 + end - it "returns nil if the key is not found when no block is given" do - m = ObjectSpace::WeakMap.new - m.delete(Object.new).should == nil - end + it "returns nil if the key is not found when no block is given" do + m = ObjectSpace::WeakMap.new + m.delete(Object.new).should == nil end end diff --git a/spec/ruby/core/proc/clone_spec.rb b/spec/ruby/core/proc/clone_spec.rb index 730dc421a87086..7d47f2cde5b4a5 100644 --- a/spec/ruby/core/proc/clone_spec.rb +++ b/spec/ruby/core/proc/clone_spec.rb @@ -5,7 +5,7 @@ describe "Proc#clone" do it_behaves_like :proc_dup, :clone - ruby_bug "cloning a frozen proc is broken on Ruby 3.3", "3.3"..."3.4" do + ruby_bug "cloning a frozen proc is broken on Ruby 3.3", ""..."3.4" do it "preserves frozen status" do proc = Proc.new { } proc.freeze @@ -14,17 +14,15 @@ end end - ruby_version_is "3.3" do - it "calls #initialize_clone on subclass" do - obj = ProcSpecs::MyProc2.new(:a, 2) { } - dup = obj.clone + it "calls #initialize_clone on subclass" do + obj = ProcSpecs::MyProc2.new(:a, 2) { } + dup = obj.clone - dup.should_not equal(obj) - dup.class.should == ProcSpecs::MyProc2 + dup.should_not equal(obj) + dup.class.should == ProcSpecs::MyProc2 - dup.first.should == :a - dup.second.should == 2 - dup.initializer.should == :clone - end + dup.first.should == :a + dup.second.should == 2 + dup.initializer.should == :clone end end diff --git a/spec/ruby/core/proc/dup_spec.rb b/spec/ruby/core/proc/dup_spec.rb index 716357d1f0e327..bdb7d8ab5a4e82 100644 --- a/spec/ruby/core/proc/dup_spec.rb +++ b/spec/ruby/core/proc/dup_spec.rb @@ -12,17 +12,15 @@ proc.dup.frozen?.should == false end - ruby_version_is "3.3" do - it "calls #initialize_dup on subclass" do - obj = ProcSpecs::MyProc2.new(:a, 2) { } - dup = obj.dup + it "calls #initialize_dup on subclass" do + obj = ProcSpecs::MyProc2.new(:a, 2) { } + dup = obj.dup - dup.should_not equal(obj) - dup.class.should == ProcSpecs::MyProc2 + dup.should_not equal(obj) + dup.class.should == ProcSpecs::MyProc2 - dup.first.should == :a - dup.second.should == 2 - dup.initializer.should == :dup - end + dup.first.should == :a + dup.second.should == 2 + dup.initializer.should == :dup end end diff --git a/spec/ruby/core/proc/lambda_spec.rb b/spec/ruby/core/proc/lambda_spec.rb index 5c3c38fc2a64c1..67ee4645cd1f0a 100644 --- a/spec/ruby/core/proc/lambda_spec.rb +++ b/spec/ruby/core/proc/lambda_spec.rb @@ -14,13 +14,6 @@ Proc.new {}.lambda?.should be_false end - ruby_version_is ""..."3.3" do - it "is preserved when passing a Proc with & to the lambda keyword" do - suppress_warning {lambda(&->{})}.lambda?.should be_true - suppress_warning {lambda(&proc{})}.lambda?.should be_false - end - end - it "is preserved when passing a Proc with & to the proc keyword" do proc(&->{}).lambda?.should be_true proc(&proc{}).lambda?.should be_false diff --git a/spec/ruby/core/process/argv0_spec.rb b/spec/ruby/core/process/argv0_spec.rb index f5aba719e96a73..9cba382c009da4 100644 --- a/spec/ruby/core/process/argv0_spec.rb +++ b/spec/ruby/core/process/argv0_spec.rb @@ -13,10 +13,8 @@ end end - ruby_bug "#19597", ""..."3.3" do - it "returns a frozen object" do - Process.argv0.should.frozen? - end + it "returns a frozen object" do + Process.argv0.should.frozen? end it "returns every time the same object" do diff --git a/spec/ruby/core/process/status/bit_and_spec.rb b/spec/ruby/core/process/status/bit_and_spec.rb index a80536462947f2..9fd1425a97600e 100644 --- a/spec/ruby/core/process/status/bit_and_spec.rb +++ b/spec/ruby/core/process/status/bit_and_spec.rb @@ -17,7 +17,7 @@ end end - ruby_version_is "3.3"..."4.0" do + ruby_version_is ""..."4.0" do it "raises an ArgumentError if mask is negative" do suppress_warning do ruby_exe("exit(0)") diff --git a/spec/ruby/core/process/status/right_shift_spec.rb b/spec/ruby/core/process/status/right_shift_spec.rb index 355aaf4c9532cb..3eaedf50550e1d 100644 --- a/spec/ruby/core/process/status/right_shift_spec.rb +++ b/spec/ruby/core/process/status/right_shift_spec.rb @@ -16,7 +16,7 @@ end end - ruby_version_is "3.3"..."4.0" do + ruby_version_is ""..."4.0" do it "raises an ArgumentError if shift value is negative" do suppress_warning do ruby_exe("exit(0)") diff --git a/spec/ruby/core/process/warmup_spec.rb b/spec/ruby/core/process/warmup_spec.rb index b562d52d226715..4530ae222c2606 100644 --- a/spec/ruby/core/process/warmup_spec.rb +++ b/spec/ruby/core/process/warmup_spec.rb @@ -1,11 +1,9 @@ require_relative '../../spec_helper' describe "Process.warmup" do - ruby_version_is "3.3" do - # The behavior is entirely implementation specific. - # Other implementations are free to just make it a noop - it "is implemented" do - Process.warmup.should == true - end + # The behavior is entirely implementation specific. + # Other implementations are free to just make it a noop + it "is implemented" do + Process.warmup.should == true end end diff --git a/spec/ruby/core/range/case_compare_spec.rb b/spec/ruby/core/range/case_compare_spec.rb index c9b253f0a585d9..7a76487d68c575 100644 --- a/spec/ruby/core/range/case_compare_spec.rb +++ b/spec/ruby/core/range/case_compare_spec.rb @@ -11,9 +11,7 @@ it_behaves_like :range_cover_and_include, :=== it_behaves_like :range_cover, :=== - ruby_bug "#19533", ""..."3.3" do - it "returns true on any value if begin and end are both nil" do - (nil..nil).should === 1 - end + it "returns true on any value if begin and end are both nil" do + (nil..nil).should === 1 end end diff --git a/spec/ruby/core/range/overlap_spec.rb b/spec/ruby/core/range/overlap_spec.rb index 9b6fc134934208..3e7d2bdda8acf9 100644 --- a/spec/ruby/core/range/overlap_spec.rb +++ b/spec/ruby/core/range/overlap_spec.rb @@ -1,89 +1,87 @@ require_relative '../../spec_helper' -ruby_version_is '3.3' do - describe "Range#overlap?" do - it "returns true if other Range overlaps self" do - (0..2).overlap?(1..3).should == true - (1..3).overlap?(0..2).should == true - (0..2).overlap?(0..2).should == true - (0..3).overlap?(1..2).should == true - (1..2).overlap?(0..3).should == true - - ('a'..'c').overlap?('b'..'d').should == true - end - - it "returns false if other Range does not overlap self" do - (0..2).overlap?(3..4).should == false - (0..2).overlap?(-4..-1).should == false - - ('a'..'c').overlap?('d'..'f').should == false - end - - it "raises TypeError when called with non-Range argument" do - -> { - (0..2).overlap?(1) - }.should raise_error(TypeError, "wrong argument type Integer (expected Range)") - end - - it "returns true when beginningless and endless Ranges overlap" do - (0..2).overlap?(..3).should == true - (0..2).overlap?(..1).should == true - (0..2).overlap?(..0).should == true - - (..3).overlap?(0..2).should == true - (..1).overlap?(0..2).should == true - (..0).overlap?(0..2).should == true - - (0..2).overlap?(-1..).should == true - (0..2).overlap?(1..).should == true - (0..2).overlap?(2..).should == true - - (-1..).overlap?(0..2).should == true - (1..).overlap?(0..2).should == true - (2..).overlap?(0..2).should == true - - (0..).overlap?(2..).should == true - (..0).overlap?(..2).should == true - end - - it "returns false when beginningless and endless Ranges do not overlap" do - (0..2).overlap?(..-1).should == false - (0..2).overlap?(3..).should == false - - (..-1).overlap?(0..2).should == false - (3..).overlap?(0..2).should == false - end - - it "returns false when Ranges are not compatible" do - (0..2).overlap?('a'..'d').should == false - end - - it "return false when self is empty" do - (2..0).overlap?(1..3).should == false - (2...2).overlap?(1..3).should == false - (1...1).overlap?(1...1).should == false - (2..0).overlap?(2..0).should == false - - ('c'..'a').overlap?('b'..'d').should == false - ('a'...'a').overlap?('b'..'d').should == false - ('b'...'b').overlap?('b'...'b').should == false - ('c'...'a').overlap?('c'...'a').should == false - end - - it "return false when other Range is empty" do - (1..3).overlap?(2..0).should == false - (1..3).overlap?(2...2).should == false - - ('b'..'d').overlap?('c'..'a').should == false - ('b'..'d').overlap?('c'...'c').should == false - end - - it "takes into account exclusive end" do - (0...2).overlap?(2..4).should == false - (2..4).overlap?(0...2).should == false - - ('a'...'c').overlap?('c'..'e').should == false - ('c'..'e').overlap?('a'...'c').should == false - end +describe "Range#overlap?" do + it "returns true if other Range overlaps self" do + (0..2).overlap?(1..3).should == true + (1..3).overlap?(0..2).should == true + (0..2).overlap?(0..2).should == true + (0..3).overlap?(1..2).should == true + (1..2).overlap?(0..3).should == true + + ('a'..'c').overlap?('b'..'d').should == true + end + + it "returns false if other Range does not overlap self" do + (0..2).overlap?(3..4).should == false + (0..2).overlap?(-4..-1).should == false + + ('a'..'c').overlap?('d'..'f').should == false + end + + it "raises TypeError when called with non-Range argument" do + -> { + (0..2).overlap?(1) + }.should raise_error(TypeError, "wrong argument type Integer (expected Range)") + end + + it "returns true when beginningless and endless Ranges overlap" do + (0..2).overlap?(..3).should == true + (0..2).overlap?(..1).should == true + (0..2).overlap?(..0).should == true + + (..3).overlap?(0..2).should == true + (..1).overlap?(0..2).should == true + (..0).overlap?(0..2).should == true + + (0..2).overlap?(-1..).should == true + (0..2).overlap?(1..).should == true + (0..2).overlap?(2..).should == true + + (-1..).overlap?(0..2).should == true + (1..).overlap?(0..2).should == true + (2..).overlap?(0..2).should == true + + (0..).overlap?(2..).should == true + (..0).overlap?(..2).should == true + end + + it "returns false when beginningless and endless Ranges do not overlap" do + (0..2).overlap?(..-1).should == false + (0..2).overlap?(3..).should == false + + (..-1).overlap?(0..2).should == false + (3..).overlap?(0..2).should == false + end + + it "returns false when Ranges are not compatible" do + (0..2).overlap?('a'..'d').should == false + end + + it "return false when self is empty" do + (2..0).overlap?(1..3).should == false + (2...2).overlap?(1..3).should == false + (1...1).overlap?(1...1).should == false + (2..0).overlap?(2..0).should == false + + ('c'..'a').overlap?('b'..'d').should == false + ('a'...'a').overlap?('b'..'d').should == false + ('b'...'b').overlap?('b'...'b').should == false + ('c'...'a').overlap?('c'...'a').should == false + end + + it "return false when other Range is empty" do + (1..3).overlap?(2..0).should == false + (1..3).overlap?(2...2).should == false + + ('b'..'d').overlap?('c'..'a').should == false + ('b'..'d').overlap?('c'...'c').should == false + end + + it "takes into account exclusive end" do + (0...2).overlap?(2..4).should == false + (2..4).overlap?(0...2).should == false + + ('a'...'c').overlap?('c'..'e').should == false + ('c'..'e').overlap?('a'...'c').should == false end end diff --git a/spec/ruby/core/range/reverse_each_spec.rb b/spec/ruby/core/range/reverse_each_spec.rb index 56390cc0da4822..16aaace6afaa60 100644 --- a/spec/ruby/core/range/reverse_each_spec.rb +++ b/spec/ruby/core/range/reverse_each_spec.rb @@ -1,102 +1,124 @@ require_relative '../../spec_helper' -ruby_version_is "3.3" do - describe "Range#reverse_each" do - it "traverses the Range in reverse order and passes each element to block" do - a = [] - (1..3).reverse_each { |i| a << i } - a.should == [3, 2, 1] +describe "Range#reverse_each" do + it "traverses the Range in reverse order and passes each element to block" do + a = [] + (1..3).reverse_each { |i| a << i } + a.should == [3, 2, 1] + + a = [] + (1...3).reverse_each { |i| a << i } + a.should == [2, 1] + end - a = [] - (1...3).reverse_each { |i| a << i } - a.should == [2, 1] - end + it "returns self" do + r = (1..3) + r.reverse_each { |x| }.should equal(r) + end - it "returns self" do - r = (1..3) - r.reverse_each { |x| }.should equal(r) - end + it "returns an Enumerator if no block given" do + enum = (1..3).reverse_each + enum.should be_an_instance_of(Enumerator) + enum.to_a.should == [3, 2, 1] + end - it "returns an Enumerator if no block given" do - enum = (1..3).reverse_each - enum.should be_an_instance_of(Enumerator) - enum.to_a.should == [3, 2, 1] - end + it "raises a TypeError for endless Ranges of Integers" do + -> { + (1..).reverse_each.take(3) + }.should raise_error(TypeError, "can't iterate from NilClass") + end - it "raises a TypeError for endless Ranges of Integers" do - -> { - (1..).reverse_each.take(3) - }.should raise_error(TypeError, "can't iterate from NilClass") - end + it "raises a TypeError for endless Ranges of non-Integers" do + -> { + ("a"..).reverse_each.take(3) + }.should raise_error(TypeError, "can't iterate from NilClass") + end - it "raises a TypeError for endless Ranges of non-Integers" do - -> { - ("a"..).reverse_each.take(3) - }.should raise_error(TypeError, "can't iterate from NilClass") + context "Integer boundaries" do + it "supports beginningless Ranges" do + (..5).reverse_each.take(3).should == [5, 4, 3] end + end - context "Integer boundaries" do - it "supports beginningless Ranges" do - (..5).reverse_each.take(3).should == [5, 4, 3] - end + context "non-Integer boundaries" do + it "uses #succ to iterate a Range of non-Integer elements" do + y = mock('y') + x = mock('x') + + x.should_receive(:succ).any_number_of_times.and_return(y) + x.should_receive(:<=>).with(y).any_number_of_times.and_return(-1) + x.should_receive(:<=>).with(x).any_number_of_times.and_return(0) + y.should_receive(:<=>).with(x).any_number_of_times.and_return(1) + y.should_receive(:<=>).with(y).any_number_of_times.and_return(0) + + a = [] + (x..y).each { |i| a << i } + a.should == [x, y] end - context "non-Integer boundaries" do - it "uses #succ to iterate a Range of non-Integer elements" do - y = mock('y') - x = mock('x') + it "uses #succ to iterate a Range of Strings" do + a = [] + ('A'..'D').reverse_each { |i| a << i } + a.should == ['D','C','B','A'] + end - x.should_receive(:succ).any_number_of_times.and_return(y) - x.should_receive(:<=>).with(y).any_number_of_times.and_return(-1) - x.should_receive(:<=>).with(x).any_number_of_times.and_return(0) - y.should_receive(:<=>).with(x).any_number_of_times.and_return(1) - y.should_receive(:<=>).with(y).any_number_of_times.and_return(0) + it "uses #succ to iterate a Range of Symbols" do + a = [] + (:A..:D).reverse_each { |i| a << i } + a.should == [:D, :C, :B, :A] + end - a = [] - (x..y).each { |i| a << i } - a.should == [x, y] - end + it "raises a TypeError when `begin` value does not respond to #succ" do + -> { (Time.now..Time.now).reverse_each { |x| x } }.should raise_error(TypeError, /can't iterate from Time/) + -> { (//..//).reverse_each { |x| x } }.should raise_error(TypeError, /can't iterate from Regexp/) + -> { ([]..[]).reverse_each { |x| x } }.should raise_error(TypeError, /can't iterate from Array/) + end - it "uses #succ to iterate a Range of Strings" do - a = [] - ('A'..'D').reverse_each { |i| a << i } - a.should == ['D','C','B','A'] - end + it "does not support beginningless Ranges" do + -> { + (..'a').reverse_each { |x| x } + }.should raise_error(TypeError, /can't iterate from NilClass/) + end + end - it "uses #succ to iterate a Range of Symbols" do - a = [] - (:A..:D).reverse_each { |i| a << i } - a.should == [:D, :C, :B, :A] - end + context "when no block is given" do + describe "returned Enumerator size" do + it "returns the Range size when Range size is finite" do + (1..3).reverse_each.size.should == 3 + (1...3).reverse_each.size.should == 2 - it "raises a TypeError when `begin` value does not respond to #succ" do - -> { (Time.now..Time.now).reverse_each { |x| x } }.should raise_error(TypeError, /can't iterate from Time/) - -> { (//..//).reverse_each { |x| x } }.should raise_error(TypeError, /can't iterate from Regexp/) - -> { ([]..[]).reverse_each { |x| x } }.should raise_error(TypeError, /can't iterate from Array/) + (1..3.3).reverse_each.size.should == 3 + (1...3.3).reverse_each.size.should == 3 end - it "does not support beginningless Ranges" do - -> { - (..'a').reverse_each { |x| x } - }.should raise_error(TypeError, /can't iterate from NilClass/) + ruby_version_is ""..."3.4" do + it "returns a size when it is not iterable" do + (1.1..3).reverse_each.size.should == 2 + (1.1..3.3).reverse_each.size.should == 3 + (1.1..nil).reverse_each.size.should == Float::INFINITY + (nil..3.3).reverse_each.size.should == Float::INFINITY + (nil..nil).reverse_each.size.should == nil + end end - end - context "when no block is given" do - describe "returned Enumerator size" do - it "returns the Range size when Range size is finite" do - (1..3).reverse_each.size.should == 3 + ruby_version_is "3.4" do + it "raises TypeError when the range is not iterable" do + -> { (1.1..3).reverse_each.size }.should raise_error(TypeError, /can't iterate from Integer/) + -> { (1.1..3.3).reverse_each.size }.should raise_error(TypeError, /can't iterate from Float/) + -> { (1.1..nil).reverse_each.size }.should raise_error(TypeError, /can't iterate from NilClass/) + -> { (nil..3.3).reverse_each.size }.should raise_error(TypeError, /can't iterate from Float/) + -> { (nil..nil).reverse_each.size }.should raise_error(TypeError, /can't iterate from NilClass/) end + end - ruby_bug "#20936", "3.4"..."4.0" do - it "returns Infinity when Range size is infinite" do - (..3).reverse_each.size.should == Float::INFINITY - end + ruby_bug "#20936", "3.4"..."4.0" do + it "returns Infinity when Range size is infinite" do + (..3).reverse_each.size.should == Float::INFINITY end + end - it "returns nil when Range size is unknown" do - ('a'..'z').reverse_each.size.should == nil - end + it "returns nil when Range size is unknown" do + ('a'..'z').reverse_each.size.should == nil end end end diff --git a/spec/ruby/core/range/to_set_spec.rb b/spec/ruby/core/range/to_set_spec.rb index 589c0e9aedec26..14e0ce1e31eac1 100644 --- a/spec/ruby/core/range/to_set_spec.rb +++ b/spec/ruby/core/range/to_set_spec.rb @@ -1,7 +1,7 @@ require_relative '../../spec_helper' require_relative '../enumerable/fixtures/classes' -describe "Enumerable#to_set" do +describe "Range#to_set" do it "returns a new Set created from self" do (1..4).to_set.should == Set[1, 2, 3, 4] (1...4).to_set.should == Set[1, 2, 3] @@ -11,45 +11,44 @@ (1..3).to_set { |x| x * x }.should == Set[1, 4, 9] end + it "raises a TypeError for a beginningless range" do + -> { + (..0).to_set + }.should raise_error(TypeError, "can't iterate from NilClass") + end + ruby_version_is "4.0" do - it "raises a RangeError if the range is infinite" do + it "raises a RangeError if the range is endless" do -> { (1..).to_set }.should raise_error(RangeError, "cannot convert endless range to a set") -> { (1...).to_set }.should raise_error(RangeError, "cannot convert endless range to a set") end end - ruby_version_is ""..."4.0" do - it "instantiates an object of provided as the first argument set class" do - set = (1..3).to_set(EnumerableSpecs::SetSubclass) - set.should be_kind_of(EnumerableSpecs::SetSubclass) - set.to_a.sort.should == [1, 2, 3] - end - end - - ruby_version_is "4.0"..."4.1" do - it "instantiates an object of provided as the first argument set class and warns" do - set = nil - proc { + context "given positional arguments" do + ruby_version_is ""..."4.0" do + it "instantiates an object of provided as the first argument set class" do set = (1..3).to_set(EnumerableSpecs::SetSubclass) - }.should complain(/Enumerable#to_set/) - set.should be_kind_of(EnumerableSpecs::SetSubclass) - set.to_a.sort.should == [1, 2, 3] + set.should be_kind_of(EnumerableSpecs::SetSubclass) + set.to_a.sort.should == [1, 2, 3] + end end - end - ruby_version_is "4.1" do - it "does not accept any positional argument" do - -> { - (1..3).to_set(EnumerableSpecs::SetSubclass) - }.should raise_error(ArgumentError, 'wrong number of arguments (given 1, expected 0)') + ruby_version_is "4.0"..."4.1" do + it "instantiates an object of provided as the first argument set class and warns" do + -> { + set = (1..3).to_set(EnumerableSpecs::SetSubclass) + set.should be_kind_of(EnumerableSpecs::SetSubclass) + set.to_a.sort.should == [1, 2, 3] + }.should complain(/warning: passing arguments to Enumerable#to_set is deprecated/) + end end - end - it "does not need explicit `require 'set'`" do - output = ruby_exe(<<~RUBY, options: '--disable-gems', args: '2>&1') - puts (1..3).to_set.to_a.inspect - RUBY - - output.chomp.should == "[1, 2, 3]" + ruby_version_is "4.1" do + it "does not accept any positional argument" do + -> { + (1..3).to_set(EnumerableSpecs::SetSubclass) + }.should raise_error(ArgumentError, "wrong number of arguments (given 1, expected 0)") + end + end end end diff --git a/spec/ruby/core/rational/ceil_spec.rb b/spec/ruby/core/rational/ceil_spec.rb index d5bdadf3b6b000..0c0327448f35c6 100644 --- a/spec/ruby/core/rational/ceil_spec.rb +++ b/spec/ruby/core/rational/ceil_spec.rb @@ -1,45 +1,48 @@ require_relative "../../spec_helper" +require_relative "../integer/shared/integer_ceil_precision" describe "Rational#ceil" do + context "with values equal to integers" do + it_behaves_like :integer_ceil_precision, :Rational + end + before do @rational = Rational(2200, 7) end describe "with no arguments (precision = 0)" do - it "returns an Integer" do - @rational.ceil.should be_kind_of(Integer) - end + it "returns the Integer value rounded toward positive infinity" do + @rational.ceil.should eql 315 - it "returns the truncated value toward positive infinity" do - @rational.ceil.should == 315 - Rational(1, 2).ceil.should == 1 - Rational(-1, 2).ceil.should == 0 + Rational(1, 2).ceil.should eql 1 + Rational(-1, 2).ceil.should eql 0 + Rational(1, 1).ceil.should eql 1 end end describe "with a precision < 0" do - it "returns an Integer" do - @rational.ceil(-2).should be_kind_of(Integer) - @rational.ceil(-1).should be_kind_of(Integer) - end + it "moves the rounding point n decimal places left, returning an Integer" do + @rational.ceil(-3).should eql 1000 + @rational.ceil(-2).should eql 400 + @rational.ceil(-1).should eql 320 - it "moves the truncation point n decimal places left" do - @rational.ceil(-3).should == 1000 - @rational.ceil(-2).should == 400 - @rational.ceil(-1).should == 320 + Rational(100, 2).ceil(-1).should eql 50 + Rational(100, 2).ceil(-2).should eql 100 + Rational(-100, 2).ceil(-1).should eql(-50) + Rational(-100, 2).ceil(-2).should eql(0) end end describe "with precision > 0" do - it "returns a Rational" do - @rational.ceil(1).should be_kind_of(Rational) - @rational.ceil(2).should be_kind_of(Rational) - end + it "moves the rounding point n decimal places right, returning a Rational" do + @rational.ceil(1).should eql Rational(3143, 10) + @rational.ceil(2).should eql Rational(31429, 100) + @rational.ceil(3).should eql Rational(157143, 500) - it "moves the truncation point n decimal places right" do - @rational.ceil(1).should == Rational(3143, 10) - @rational.ceil(2).should == Rational(31429, 100) - @rational.ceil(3).should == Rational(157143, 500) + Rational(100, 2).ceil(1).should eql Rational(50, 1) + Rational(100, 2).ceil(2).should eql Rational(50, 1) + Rational(-100, 2).ceil(1).should eql Rational(-50, 1) + Rational(-100, 2).ceil(2).should eql Rational(-50, 1) end end end diff --git a/spec/ruby/core/rational/exponent_spec.rb b/spec/ruby/core/rational/exponent_spec.rb index 65fbf2ed1ca895..1f8a03740cc087 100644 --- a/spec/ruby/core/rational/exponent_spec.rb +++ b/spec/ruby/core/rational/exponent_spec.rb @@ -108,37 +108,37 @@ it "raises an ArgumentError when self is > 1" do -> { (Rational(2) ** bignum_value) - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, "exponent is too large") -> { (Rational(fixnum_max) ** bignum_value) - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, "exponent is too large") end it "raises an ArgumentError when self is > 1 and the exponent is negative" do -> { (Rational(2) ** -bignum_value) - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, "exponent is too large") -> { (Rational(fixnum_max) ** -bignum_value) - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, "exponent is too large") end it "raises an ArgumentError when self is < -1" do -> { (Rational(-2) ** bignum_value) - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, "exponent is too large") -> { (Rational(fixnum_min) ** bignum_value) - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, "exponent is too large") end it "raises an ArgumentError when self is < -1 and the exponent is negative" do -> { (Rational(-2) ** -bignum_value) - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, "exponent is too large") -> { (Rational(fixnum_min) ** -bignum_value) - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, "exponent is too large") end end diff --git a/spec/ruby/core/rational/floor_spec.rb b/spec/ruby/core/rational/floor_spec.rb index 8068aaf119e70f..5108e363f7a67c 100644 --- a/spec/ruby/core/rational/floor_spec.rb +++ b/spec/ruby/core/rational/floor_spec.rb @@ -1,45 +1,49 @@ require_relative "../../spec_helper" +require_relative "../integer/shared/integer_floor_precision" describe "Rational#floor" do + context "with values equal to integers" do + it_behaves_like :integer_floor_precision, :Rational + end + before do @rational = Rational(2200, 7) end describe "with no arguments (precision = 0)" do - it "returns an integer" do - @rational.floor.should be_kind_of(Integer) - end - it "returns the truncated value toward negative infinity" do - @rational.floor.should == 314 - Rational(1, 2).floor.should == 0 - Rational(-1, 2).floor.should == -1 + it "returns the Integer value rounded toward negative infinity" do + @rational.floor.should eql 314 + + Rational(1, 2).floor.should eql 0 + Rational(-1, 2).floor.should eql(-1) + Rational(1, 1).floor.should eql 1 end end describe "with a precision < 0" do - it "returns an integer" do - @rational.floor(-2).should be_kind_of(Integer) - @rational.floor(-1).should be_kind_of(Integer) - end + it "moves the rounding point n decimal places left, returning an Integer" do + @rational.floor(-3).should eql 0 + @rational.floor(-2).should eql 300 + @rational.floor(-1).should eql 310 - it "moves the truncation point n decimal places left" do - @rational.floor(-3).should == 0 - @rational.floor(-2).should == 300 - @rational.floor(-1).should == 310 + Rational(100, 2).floor(-1).should eql 50 + Rational(100, 2).floor(-2).should eql 0 + Rational(-100, 2).floor(-1).should eql(-50) + Rational(-100, 2).floor(-2).should eql(-100) end end describe "with a precision > 0" do - it "returns a Rational" do - @rational.floor(1).should be_kind_of(Rational) - @rational.floor(2).should be_kind_of(Rational) - end + it "moves the rounding point n decimal places right, returning a Rational" do + @rational.floor(1).should eql Rational(1571, 5) + @rational.floor(2).should eql Rational(7857, 25) + @rational.floor(3).should eql Rational(62857, 200) - it "moves the truncation point n decimal places right" do - @rational.floor(1).should == Rational(1571, 5) - @rational.floor(2).should == Rational(7857, 25) - @rational.floor(3).should == Rational(62857, 200) + Rational(100, 2).floor(1).should eql Rational(50, 1) + Rational(100, 2).floor(2).should eql Rational(50, 1) + Rational(-100, 2).floor(1).should eql Rational(-50, 1) + Rational(-100, 2).floor(2).should eql Rational(-50, 1) end end end diff --git a/spec/ruby/core/refinement/refined_class_spec.rb b/spec/ruby/core/refinement/refined_class_spec.rb index 60a58380ccf00b..b532d9a7738cca 100644 --- a/spec/ruby/core/refinement/refined_class_spec.rb +++ b/spec/ruby/core/refinement/refined_class_spec.rb @@ -2,11 +2,7 @@ require_relative 'shared/target' describe "Refinement#refined_class" do - ruby_version_is ""..."3.3" do - it_behaves_like :refinement_target, :refined_class - end - - ruby_version_is "3.3"..."3.4" do + ruby_version_is ""..."3.4" do it "has been deprecated in favour of Refinement#target" do refinement_int = nil diff --git a/spec/ruby/core/refinement/target_spec.rb b/spec/ruby/core/refinement/target_spec.rb index fee9588a96ed65..8bd816aea622dd 100644 --- a/spec/ruby/core/refinement/target_spec.rb +++ b/spec/ruby/core/refinement/target_spec.rb @@ -2,7 +2,5 @@ require_relative 'shared/target' describe "Refinement#target" do - ruby_version_is "3.3" do - it_behaves_like :refinement_target, :target - end + it_behaves_like :refinement_target, :target end diff --git a/spec/ruby/core/regexp/linear_time_spec.rb b/spec/ruby/core/regexp/linear_time_spec.rb index cf9e73c37c2b64..2f3f81ed207236 100644 --- a/spec/ruby/core/regexp/linear_time_spec.rb +++ b/spec/ruby/core/regexp/linear_time_spec.rb @@ -25,9 +25,7 @@ }.should complain(/warning: flags ignored/) end - ruby_version_is "3.3" do - it "returns true for positive lookarounds" do - Regexp.linear_time?(/(?:(?=a*)a)*/).should == true - end + it "returns true for positive lookarounds" do + Regexp.linear_time?(/(?:(?=a*)a)*/).should == true end end diff --git a/spec/ruby/core/set/flatten_spec.rb b/spec/ruby/core/set/flatten_spec.rb index f2cb3dfa524a35..b26bc8481af58f 100644 --- a/spec/ruby/core/set/flatten_spec.rb +++ b/spec/ruby/core/set/flatten_spec.rb @@ -46,14 +46,4 @@ (set = Set[]) << set -> { set.flatten! }.should raise_error(ArgumentError) end - - version_is(set_version, ""..."1.1.0") do #ruby_version_is ""..."3.3" do - ruby_version_is ""..."4.0" do - context "when Set contains a Set-like object" do - it "flattens self, including Set-like objects" do - Set[SetSpecs::SetLike.new([1])].flatten!.should == Set[1] - end - end - end - end end diff --git a/spec/ruby/core/set/merge_spec.rb b/spec/ruby/core/set/merge_spec.rb index 0c6ed276700e7d..bf945cdcc02238 100644 --- a/spec/ruby/core/set/merge_spec.rb +++ b/spec/ruby/core/set/merge_spec.rb @@ -23,15 +23,7 @@ end end - ruby_version_is ""..."3.3" do - it "accepts only a single argument" do - -> { Set[].merge([], []) }.should raise_error(ArgumentError, "wrong number of arguments (given 2, expected 1)") - end - end - - ruby_version_is "3.3" do - it "accepts multiple arguments" do - Set[:a, :b].merge(Set[:b, :c], [:d]).should == Set[:a, :b, :c, :d] - end + it "accepts multiple arguments" do + Set[:a, :b].merge(Set[:b, :c], [:d]).should == Set[:a, :b, :c, :d] end end diff --git a/spec/ruby/core/set/proper_subset_spec.rb b/spec/ruby/core/set/proper_subset_spec.rb index fb7848c0015200..6f99447019b852 100644 --- a/spec/ruby/core/set/proper_subset_spec.rb +++ b/spec/ruby/core/set/proper_subset_spec.rb @@ -32,14 +32,4 @@ -> { Set[].proper_subset?("test") }.should raise_error(ArgumentError) -> { Set[].proper_subset?(Object.new) }.should raise_error(ArgumentError) end - - version_is(set_version, ""..."1.1.0") do #ruby_version_is ""..."3.3" do - ruby_version_is ""..."4.0" do - context "when comparing to a Set-like object" do - it "returns true if passed a Set-like object that self is a proper subset of" do - Set[1, 2, 3].proper_subset?(SetSpecs::SetLike.new([1, 2, 3, 4])).should be_true - end - end - end - end end diff --git a/spec/ruby/core/set/subset_spec.rb b/spec/ruby/core/set/subset_spec.rb index 112bd9b38adc12..da80d174da4fa1 100644 --- a/spec/ruby/core/set/subset_spec.rb +++ b/spec/ruby/core/set/subset_spec.rb @@ -32,14 +32,4 @@ -> { Set[].subset?("test") }.should raise_error(ArgumentError) -> { Set[].subset?(Object.new) }.should raise_error(ArgumentError) end - - version_is(set_version, ""..."1.1.0") do #ruby_version_is ""..."3.3" do - ruby_version_is ""..."4.0" do - context "when comparing to a Set-like object" do - it "returns true if passed a Set-like object that self is a subset of" do - Set[1, 2, 3].subset?(SetSpecs::SetLike.new([1, 2, 3, 4])).should be_true - end - end - end - end end diff --git a/spec/ruby/core/string/append_as_bytes_spec.rb b/spec/ruby/core/string/append_as_bytes_spec.rb index b1703e5f89baf9..def663d5ce2239 100644 --- a/spec/ruby/core/string/append_as_bytes_spec.rb +++ b/spec/ruby/core/string/append_as_bytes_spec.rb @@ -7,14 +7,16 @@ -> { str.append_as_bytes("\xE2\x82") }.should raise_error(FrozenError) end - it "allows creating broken strings" do + it "allows creating broken strings in UTF8" do str = +"hello" str.append_as_bytes("\xE2\x82") str.valid_encoding?.should == false str.append_as_bytes("\xAC") str.valid_encoding?.should == true + end + it "allows creating broken strings in UTF_32" do str = "abc".encode(Encoding::UTF_32LE) str.append_as_bytes("def") str.encoding.should == Encoding::UTF_32LE diff --git a/spec/ruby/core/string/bytesplice_spec.rb b/spec/ruby/core/string/bytesplice_spec.rb index 2c770e340aad27..cfd9e3ea9a7f39 100644 --- a/spec/ruby/core/string/bytesplice_spec.rb +++ b/spec/ruby/core/string/bytesplice_spec.rb @@ -57,77 +57,75 @@ -> { s.bytesplice(2, 1, "xxx") }.should raise_error(FrozenError, "can't modify frozen String: \"hello\"") end - ruby_version_is "3.3" do - it "raises IndexError when str_index is less than -bytesize" do - -> { "hello".bytesplice(2, 1, "HELLO", -6, 0) }.should raise_error(IndexError, "index -6 out of string") - end - - it "raises IndexError when str_index is greater than bytesize" do - -> { "hello".bytesplice(2, 1, "HELLO", 6, 0) }.should raise_error(IndexError, "index 6 out of string") - end - - it "raises IndexError for negative str length" do - -> { "abc".bytesplice(0, 1, "", 0, -2) }.should raise_error(IndexError, "negative length -2") - end - - it "replaces with integer str indices" do - "hello".bytesplice(1, 2, "HELLO", -5, 0).should == "hlo" - "hello".bytesplice(1, 2, "HELLO", 0, 0).should == "hlo" - "hello".bytesplice(1, 2, "HELLO", 0, 1).should == "hHlo" - "hello".bytesplice(1, 2, "HELLO", 0, 5).should == "hHELLOlo" - "hello".bytesplice(1, 2, "HELLO", 0, 6).should == "hHELLOlo" - end - - it "raises RangeError when str range left boundary is less than -bytesize" do - -> { "hello".bytesplice(0..1, "HELLO", -6...-6) }.should raise_error(RangeError, "-6...-6 out of range") - end - - it "replaces with str ranges" do - "hello".bytesplice(1..2, "HELLO", -5...-5).should == "hlo" - "hello".bytesplice(1..2, "HELLO", 0...0).should == "hlo" - "hello".bytesplice(1..2, "HELLO", 0..0).should == "hHlo" - "hello".bytesplice(1..2, "HELLO", 0...1).should == "hHlo" - "hello".bytesplice(1..2, "HELLO", 0..1).should == "hHElo" - "hello".bytesplice(1..2, "HELLO", 0..-1).should == "hHELLOlo" - "hello".bytesplice(1..2, "HELLO", 0...5).should == "hHELLOlo" - "hello".bytesplice(1..2, "HELLO", 0...6).should == "hHELLOlo" - end - - it "raises ArgumentError when integer str index is provided without str length argument" do - -> { "hello".bytesplice(0, 1, "xxx", 0) }.should raise_error(ArgumentError, "wrong number of arguments (given 4, expected 2, 3, or 5)") - end - - it "replaces on an empty string with str index/length" do - "".bytesplice(0, 0, "", 0, 0).should == "" - "".bytesplice(0, 0, "xxx", 0, 1).should == "x" - end - - it "mutates self with substring and str index/length" do - s = "hello" - s.bytesplice(2, 1, "xxx", 1, 2).should.equal?(s) - s.should.eql?("hexxlo") - end - - it "raises when string is frozen and str index/length" do - s = "hello".freeze - -> { s.bytesplice(2, 1, "xxx", 0, 1) }.should raise_error(FrozenError, "can't modify frozen String: \"hello\"") - end - - it "replaces on an empty string with str range" do - "".bytesplice(0..0, "", 0..0).should == "" - "".bytesplice(0..0, "xyz", 0..1).should == "xy" - end - - it "mutates self with substring and str range" do - s = "hello" - s.bytesplice(2..2, "xyz", 1..2).should.equal?(s) - s.should.eql?("heyzlo") - end - - it "raises when string is frozen and str range" do - s = "hello".freeze - -> { s.bytesplice(2..2, "yzx", 0..1) }.should raise_error(FrozenError, "can't modify frozen String: \"hello\"") - end + it "raises IndexError when str_index is less than -bytesize" do + -> { "hello".bytesplice(2, 1, "HELLO", -6, 0) }.should raise_error(IndexError, "index -6 out of string") + end + + it "raises IndexError when str_index is greater than bytesize" do + -> { "hello".bytesplice(2, 1, "HELLO", 6, 0) }.should raise_error(IndexError, "index 6 out of string") + end + + it "raises IndexError for negative str length" do + -> { "abc".bytesplice(0, 1, "", 0, -2) }.should raise_error(IndexError, "negative length -2") + end + + it "replaces with integer str indices" do + "hello".bytesplice(1, 2, "HELLO", -5, 0).should == "hlo" + "hello".bytesplice(1, 2, "HELLO", 0, 0).should == "hlo" + "hello".bytesplice(1, 2, "HELLO", 0, 1).should == "hHlo" + "hello".bytesplice(1, 2, "HELLO", 0, 5).should == "hHELLOlo" + "hello".bytesplice(1, 2, "HELLO", 0, 6).should == "hHELLOlo" + end + + it "raises RangeError when str range left boundary is less than -bytesize" do + -> { "hello".bytesplice(0..1, "HELLO", -6...-6) }.should raise_error(RangeError, "-6...-6 out of range") + end + + it "replaces with str ranges" do + "hello".bytesplice(1..2, "HELLO", -5...-5).should == "hlo" + "hello".bytesplice(1..2, "HELLO", 0...0).should == "hlo" + "hello".bytesplice(1..2, "HELLO", 0..0).should == "hHlo" + "hello".bytesplice(1..2, "HELLO", 0...1).should == "hHlo" + "hello".bytesplice(1..2, "HELLO", 0..1).should == "hHElo" + "hello".bytesplice(1..2, "HELLO", 0..-1).should == "hHELLOlo" + "hello".bytesplice(1..2, "HELLO", 0...5).should == "hHELLOlo" + "hello".bytesplice(1..2, "HELLO", 0...6).should == "hHELLOlo" + end + + it "raises ArgumentError when integer str index is provided without str length argument" do + -> { "hello".bytesplice(0, 1, "xxx", 0) }.should raise_error(ArgumentError, "wrong number of arguments (given 4, expected 2, 3, or 5)") + end + + it "replaces on an empty string with str index/length" do + "".bytesplice(0, 0, "", 0, 0).should == "" + "".bytesplice(0, 0, "xxx", 0, 1).should == "x" + end + + it "mutates self with substring and str index/length" do + s = "hello" + s.bytesplice(2, 1, "xxx", 1, 2).should.equal?(s) + s.should.eql?("hexxlo") + end + + it "raises when string is frozen and str index/length" do + s = "hello".freeze + -> { s.bytesplice(2, 1, "xxx", 0, 1) }.should raise_error(FrozenError, "can't modify frozen String: \"hello\"") + end + + it "replaces on an empty string with str range" do + "".bytesplice(0..0, "", 0..0).should == "" + "".bytesplice(0..0, "xyz", 0..1).should == "xy" + end + + it "mutates self with substring and str range" do + s = "hello" + s.bytesplice(2..2, "xyz", 1..2).should.equal?(s) + s.should.eql?("heyzlo") + end + + it "raises when string is frozen and str range" do + s = "hello".freeze + -> { s.bytesplice(2..2, "yzx", 0..1) }.should raise_error(FrozenError, "can't modify frozen String: \"hello\"") end end @@ -201,94 +199,92 @@ result.encoding.should == Encoding::UTF_8 end - ruby_version_is "3.3" do - it "raises IndexError when str_index is out of byte size boundary" do - -> { "こんにちは".bytesplice(3, 3, "こんにちは", -16, 0) }.should raise_error(IndexError, "index -16 out of string") - end - - it "raises IndexError when str_index is not on a codepoint boundary" do - -> { "こんにちは".bytesplice(3, 3, "こんにちは", 1, 0) }.should raise_error(IndexError, "offset 1 does not land on character boundary") - end - - it "raises IndexError when str_length is not matching the codepoint boundary" do - -> { "こんにちは".bytesplice(3, 3, "こんにちは", 0, 1) }.should raise_error(IndexError, "offset 1 does not land on character boundary") - -> { "こんにちは".bytesplice(3, 3, "こんにちは", 0, 2) }.should raise_error(IndexError, "offset 2 does not land on character boundary") - end - - it "replaces with integer str indices" do - "こんにちは".bytesplice(3, 3, "こんにちは", -15, 0).should == "こにちは" - "こんにちは".bytesplice(3, 3, "こんにちは", 0, 0).should == "こにちは" - "こんにちは".bytesplice(3, 3, "こんにちは", 0, 3).should == "ここにちは" - "こんにちは".bytesplice(3, 3, "はは", 3, 3).should == "こはにちは" - "こんにちは".bytesplice(3, 3, "こんにちは", 15, 0).should == "こにちは" - end - - it "replaces with str range" do - "こんにちは".bytesplice(0..2, "こんにちは", -15...-16).should == "んにちは" - "こんにちは".bytesplice(0..2, "こんにちは", 0...0).should == "んにちは" - "こんにちは".bytesplice(0..2, "こんにちは", 3..5).should == "んんにちは" - "こんにちは".bytesplice(0..2, "こんにちは", 3...6).should == "んんにちは" - "こんにちは".bytesplice(0..2, "こんにちは", 3..8).should == "んにんにちは" - "こんにちは".bytesplice(0..2, "こんにちは", 0..-1).should == "こんにちはんにちは" - "こんにちは".bytesplice(0..2, "こんにちは", 0...15).should == "こんにちはんにちは" - "こんにちは".bytesplice(0..2, "こんにちは", 0...18).should == "こんにちはんにちは" - end - - it "treats negative length for str range as 0" do - "こんにちは".bytesplice(0..2, "こんにちは", 0...-100).should == "んにちは" - "こんにちは".bytesplice(0..2, "こんにちは", 3...-100).should == "んにちは" - "こんにちは".bytesplice(0..2, "こんにちは", -15...-100).should == "んにちは" - end - - it "raises when ranges not match codepoint boundaries in str" do - -> { "こんにちは".bytesplice(3...3, "こ", 0..0) }.should raise_error(IndexError, "offset 1 does not land on character boundary") - -> { "こんにちは".bytesplice(3...3, "こ", 0..1) }.should raise_error(IndexError, "offset 2 does not land on character boundary") - # Begin is incorrect - -> { "こんにちは".bytesplice(3...3, "こんにちは", -4..-1) }.should raise_error(IndexError, "offset 11 does not land on character boundary") - -> { "こんにちは".bytesplice(3...3, "こんにちは", -5..-1) }.should raise_error(IndexError, "offset 10 does not land on character boundary") - # End is incorrect - -> { "こんにちは".bytesplice(3...3, "こんにちは", -3..-2) }.should raise_error(IndexError, "offset 14 does not land on character boundary") - -> { "こんにちは".bytesplice(3...3, "こんにちは", -3..-3) }.should raise_error(IndexError, "offset 13 does not land on character boundary") - end - - it "deals with a different encoded argument with str index/length" do - s = "こんにちは" - s.encoding.should == Encoding::UTF_8 - sub = "goodbye" - sub.force_encoding(Encoding::US_ASCII) - - result = s.bytesplice(3, 3, sub, 0, 3) - result.should == "こgooにちは" - result.encoding.should == Encoding::UTF_8 - - s = "hello" - s.force_encoding(Encoding::US_ASCII) - sub = "こんにちは" - sub.encoding.should == Encoding::UTF_8 - - result = s.bytesplice(1, 2, sub, 3, 3) - result.should == "hんlo" - result.encoding.should == Encoding::UTF_8 - end - - it "deals with a different encoded argument with str range" do - s = "こんにちは" - s.encoding.should == Encoding::UTF_8 - sub = "goodbye" - sub.force_encoding(Encoding::US_ASCII) - - result = s.bytesplice(3..5, sub, 0..2) - result.should == "こgooにちは" - result.encoding.should == Encoding::UTF_8 - - s = "hello" - s.force_encoding(Encoding::US_ASCII) - sub = "こんにちは" - sub.encoding.should == Encoding::UTF_8 - - result = s.bytesplice(1..2, sub, 3..5) - result.should == "hんlo" - result.encoding.should == Encoding::UTF_8 - end + it "raises IndexError when str_index is out of byte size boundary" do + -> { "こんにちは".bytesplice(3, 3, "こんにちは", -16, 0) }.should raise_error(IndexError, "index -16 out of string") + end + + it "raises IndexError when str_index is not on a codepoint boundary" do + -> { "こんにちは".bytesplice(3, 3, "こんにちは", 1, 0) }.should raise_error(IndexError, "offset 1 does not land on character boundary") + end + + it "raises IndexError when str_length is not matching the codepoint boundary" do + -> { "こんにちは".bytesplice(3, 3, "こんにちは", 0, 1) }.should raise_error(IndexError, "offset 1 does not land on character boundary") + -> { "こんにちは".bytesplice(3, 3, "こんにちは", 0, 2) }.should raise_error(IndexError, "offset 2 does not land on character boundary") + end + + it "replaces with integer str indices" do + "こんにちは".bytesplice(3, 3, "こんにちは", -15, 0).should == "こにちは" + "こんにちは".bytesplice(3, 3, "こんにちは", 0, 0).should == "こにちは" + "こんにちは".bytesplice(3, 3, "こんにちは", 0, 3).should == "ここにちは" + "こんにちは".bytesplice(3, 3, "はは", 3, 3).should == "こはにちは" + "こんにちは".bytesplice(3, 3, "こんにちは", 15, 0).should == "こにちは" + end + + it "replaces with str range" do + "こんにちは".bytesplice(0..2, "こんにちは", -15...-16).should == "んにちは" + "こんにちは".bytesplice(0..2, "こんにちは", 0...0).should == "んにちは" + "こんにちは".bytesplice(0..2, "こんにちは", 3..5).should == "んんにちは" + "こんにちは".bytesplice(0..2, "こんにちは", 3...6).should == "んんにちは" + "こんにちは".bytesplice(0..2, "こんにちは", 3..8).should == "んにんにちは" + "こんにちは".bytesplice(0..2, "こんにちは", 0..-1).should == "こんにちはんにちは" + "こんにちは".bytesplice(0..2, "こんにちは", 0...15).should == "こんにちはんにちは" + "こんにちは".bytesplice(0..2, "こんにちは", 0...18).should == "こんにちはんにちは" + end + + it "treats negative length for str range as 0" do + "こんにちは".bytesplice(0..2, "こんにちは", 0...-100).should == "んにちは" + "こんにちは".bytesplice(0..2, "こんにちは", 3...-100).should == "んにちは" + "こんにちは".bytesplice(0..2, "こんにちは", -15...-100).should == "んにちは" + end + + it "raises when ranges not match codepoint boundaries in str" do + -> { "こんにちは".bytesplice(3...3, "こ", 0..0) }.should raise_error(IndexError, "offset 1 does not land on character boundary") + -> { "こんにちは".bytesplice(3...3, "こ", 0..1) }.should raise_error(IndexError, "offset 2 does not land on character boundary") + # Begin is incorrect + -> { "こんにちは".bytesplice(3...3, "こんにちは", -4..-1) }.should raise_error(IndexError, "offset 11 does not land on character boundary") + -> { "こんにちは".bytesplice(3...3, "こんにちは", -5..-1) }.should raise_error(IndexError, "offset 10 does not land on character boundary") + # End is incorrect + -> { "こんにちは".bytesplice(3...3, "こんにちは", -3..-2) }.should raise_error(IndexError, "offset 14 does not land on character boundary") + -> { "こんにちは".bytesplice(3...3, "こんにちは", -3..-3) }.should raise_error(IndexError, "offset 13 does not land on character boundary") + end + + it "deals with a different encoded argument with str index/length" do + s = "こんにちは" + s.encoding.should == Encoding::UTF_8 + sub = "goodbye" + sub.force_encoding(Encoding::US_ASCII) + + result = s.bytesplice(3, 3, sub, 0, 3) + result.should == "こgooにちは" + result.encoding.should == Encoding::UTF_8 + + s = "hello" + s.force_encoding(Encoding::US_ASCII) + sub = "こんにちは" + sub.encoding.should == Encoding::UTF_8 + + result = s.bytesplice(1, 2, sub, 3, 3) + result.should == "hんlo" + result.encoding.should == Encoding::UTF_8 + end + + it "deals with a different encoded argument with str range" do + s = "こんにちは" + s.encoding.should == Encoding::UTF_8 + sub = "goodbye" + sub.force_encoding(Encoding::US_ASCII) + + result = s.bytesplice(3..5, sub, 0..2) + result.should == "こgooにちは" + result.encoding.should == Encoding::UTF_8 + + s = "hello" + s.force_encoding(Encoding::US_ASCII) + sub = "こんにちは" + sub.encoding.should == Encoding::UTF_8 + + result = s.bytesplice(1..2, sub, 3..5) + result.should == "hんlo" + result.encoding.should == Encoding::UTF_8 end end diff --git a/spec/ruby/core/string/index_spec.rb b/spec/ruby/core/string/index_spec.rb index 835263a2cd58c2..01e6a6a4009720 100644 --- a/spec/ruby/core/string/index_spec.rb +++ b/spec/ruby/core/string/index_spec.rb @@ -231,15 +231,13 @@ $~.should == nil end - ruby_bug "#20421", ""..."3.3" do - it "always clear $~" do - "a".index(/a/) - $~.should_not == nil - - string = "blablabla" - string.index(/bla/, string.length + 1) - $~.should == nil - end + it "always clear $~" do + "a".index(/a/) + $~.should_not == nil + + string = "blablabla" + string.index(/bla/, string.length + 1) + $~.should == nil end it "starts the search at the given offset" do @@ -330,21 +328,10 @@ "われわわれ".index(/わ/, 3).should == 3 end - ruby_bug "#19763", ""..."3.3.0" do - it "raises an Encoding::CompatibilityError if the encodings are incompatible" do - re = Regexp.new "れ".encode(Encoding::EUC_JP) - -> do - "あれ".index re - end.should raise_error(Encoding::CompatibilityError, "incompatible encoding regexp match (EUC-JP regexp with UTF-8 string)") - end - end - - # The exception message was incorrectly "incompatible character encodings: UTF-8 and EUC-JP" before 3.3.0 - # Still test that the right exception class is used before that. it "raises an Encoding::CompatibilityError if the encodings are incompatible" do re = Regexp.new "れ".encode(Encoding::EUC_JP) -> do "あれ".index re - end.should raise_error(Encoding::CompatibilityError) + end.should raise_error(Encoding::CompatibilityError, "incompatible encoding regexp match (EUC-JP regexp with UTF-8 string)") end end diff --git a/spec/ruby/core/string/shared/chars.rb b/spec/ruby/core/string/shared/chars.rb index c730643cf49874..74a32fb513f024 100644 --- a/spec/ruby/core/string/shared/chars.rb +++ b/spec/ruby/core/string/shared/chars.rb @@ -14,7 +14,6 @@ s.send(@method){}.should equal(s) end - it "is unicode aware" do "\303\207\342\210\202\303\251\306\222g".send(@method).to_a.should == ["\303\207", "\342\210\202", "\303\251", "\306\222", "g"] @@ -63,4 +62,25 @@ [0xA2].pack('C').force_encoding('SJIS') ] end + + it "returns individual chars for dummy encodings" do + "ab".dup.force_encoding(Encoding::UTF_7).send(@method).to_a.should == [ + "\x61".dup.force_encoding(Encoding::UTF_7), + "\x62".dup.force_encoding(Encoding::UTF_7) + ] + + "abcd".dup.force_encoding(Encoding::UTF_16).send(@method).to_a.should == [ + "\x61".dup.force_encoding(Encoding::UTF_16), + "\x62".dup.force_encoding(Encoding::UTF_16), + "\x63".dup.force_encoding(Encoding::UTF_16), + "\x64".dup.force_encoding(Encoding::UTF_16) + ] + + "abcd".dup.force_encoding(Encoding::UTF_32).send(@method).to_a.should == [ + "\x61".dup.force_encoding(Encoding::UTF_32), + "\x62".dup.force_encoding(Encoding::UTF_32), + "\x63".dup.force_encoding(Encoding::UTF_32), + "\x64".dup.force_encoding(Encoding::UTF_32) + ] + end end diff --git a/spec/ruby/core/string/shared/codepoints.rb b/spec/ruby/core/string/shared/codepoints.rb index 1c28ba3d5e22ff..ecdf7d719db553 100644 --- a/spec/ruby/core/string/shared/codepoints.rb +++ b/spec/ruby/core/string/shared/codepoints.rb @@ -59,4 +59,9 @@ s.ascii_only?.should be_true s.send(@method).to_a.should == s.bytes.to_a end + + it "returns individual bytes for dummy encodings UTF-16 and UTF-32" do + "abcd".dup.force_encoding(Encoding::UTF_16).send(@method).to_a.should == [97, 98, 99, 100] + "abcd".dup.force_encoding(Encoding::UTF_32).send(@method).to_a.should == [97, 98, 99, 100] + end end diff --git a/spec/ruby/core/string/shared/each_line.rb b/spec/ruby/core/string/shared/each_line.rb index 231a6d9d4ff3a2..c2f3abfa80e0a6 100644 --- a/spec/ruby/core/string/shared/each_line.rb +++ b/spec/ruby/core/string/shared/each_line.rb @@ -159,4 +159,18 @@ a.should == ["hello\r\n", "world\r\n"] end end + + it "does not split lines for dummy UTF-16" do + "a\nb".encode(Encoding::UTF_16).lines.should == [ + "\xFE\xFF\x00\x61\x00\n\x00\x62".dup.force_encoding(Encoding::UTF_16) + ] + + str = "\x00\n\n\x00".dup.force_encoding(Encoding::UTF_16) + str.lines.should == [str] + end + + it "raises Encoding::ConverterNotFoundError for dummy UTF-7" do + str = "a\nb".dup.force_encoding(Encoding::UTF_7) + -> { str.lines }.should raise_error(Encoding::ConverterNotFoundError) + end end diff --git a/spec/ruby/core/string/shared/grapheme_clusters.rb b/spec/ruby/core/string/shared/grapheme_clusters.rb index 8b666868b1df68..985b558f08a03e 100644 --- a/spec/ruby/core/string/shared/grapheme_clusters.rb +++ b/spec/ruby/core/string/shared/grapheme_clusters.rb @@ -9,6 +9,15 @@ a.should == ['a', 'b', "\u{1f3f3}\u{fe0f}\u{200d}\u{1f308}", "\u{1F43E}"] end + it "returns grapheme clusters for various UTF encodings" do + [Encoding::UTF_16LE, Encoding::UTF_16BE, Encoding::UTF_32LE, Encoding::UTF_32BE].each do |enc| + a = [] + # test string: abc[rainbow flag emoji][paw prints] + "ab\u{1f3f3}\u{fe0f}\u{200d}\u{1f308}\u{1F43E}".encode(enc).send(@method) { |c| a << c } + a.should == ['a', 'b', "\u{1f3f3}\u{fe0f}\u{200d}\u{1f308}", "\u{1F43E}"].map { |s| s.encode(enc) } + end + end + it "returns self" do s = StringSpecs::MyString.new "ab\u{1f3f3}\u{fe0f}\u{200d}\u{1f308}\u{1F43E}" s.send(@method) {}.should equal(s) diff --git a/spec/ruby/core/string/start_with_spec.rb b/spec/ruby/core/string/start_with_spec.rb index 35e33b46a668ee..8b0ba6b5a7ec3b 100644 --- a/spec/ruby/core/string/start_with_spec.rb +++ b/spec/ruby/core/string/start_with_spec.rb @@ -11,17 +11,8 @@ "\xA9".should.start_with?("\xA9") # A9 is not a character head for UTF-8 end - ruby_version_is ""..."3.3" do - it "does not check we are matching only part of a character" do - "\xe3\x81\x82".size.should == 1 - "\xe3\x81\x82".should.start_with?("\xe3") - end - end - - ruby_version_is "3.3" do # #19784 - it "checks we are matching only part of a character" do - "\xe3\x81\x82".size.should == 1 - "\xe3\x81\x82".should_not.start_with?("\xe3") - end + it "checks we are matching only part of a character" do + "\xe3\x81\x82".size.should == 1 + "\xe3\x81\x82".should_not.start_with?("\xe3") end end diff --git a/spec/ruby/core/string/tr_s_spec.rb b/spec/ruby/core/string/tr_s_spec.rb index dd72da440c93d5..693ff8ace21bb5 100644 --- a/spec/ruby/core/string/tr_s_spec.rb +++ b/spec/ruby/core/string/tr_s_spec.rb @@ -18,13 +18,11 @@ "hello ^--^".tr_s("---", "_").should == "hello ^_^" end - ruby_bug "#19769", ""..."3.3" do - it "accepts c1-c1 notation to denote range of one character" do - "hello".tr_s('e-e', 'x').should == "hxllo" - "123456789".tr_s("2-23","xy").should == "1xy456789" - "hello ^-^".tr_s("e-", "a-a_").should == "hallo ^_^" - "hello ^-^".tr_s("---o", "_a").should == "hella ^_^" - end + it "accepts c1-c1 notation to denote range of one character" do + "hello".tr_s('e-e', 'x').should == "hxllo" + "123456789".tr_s("2-23","xy").should == "1xy456789" + "hello ^-^".tr_s("e-", "a-a_").should == "hallo ^_^" + "hello ^-^".tr_s("---o", "_a").should == "hella ^_^" end it "pads to_str with its last char if it is shorter than from_string" do diff --git a/spec/ruby/core/string/tr_spec.rb b/spec/ruby/core/string/tr_spec.rb index 75841a974fcc53..8478ccc9d2879c 100644 --- a/spec/ruby/core/string/tr_spec.rb +++ b/spec/ruby/core/string/tr_spec.rb @@ -17,13 +17,11 @@ "hello ^-^".tr("---", "_").should == "hello ^_^" end - ruby_bug "#19769", ""..."3.3" do - it "accepts c1-c1 notation to denote range of one character" do - "hello".tr('e-e', 'x').should == "hxllo" - "123456789".tr("2-23","xy").should == "1xy456789" - "hello ^-^".tr("e-", "a-a_").should == "hallo ^_^" - "hello ^-^".tr("---o", "_a").should == "hella ^_^" - end + it "accepts c1-c1 notation to denote range of one character" do + "hello".tr('e-e', 'x').should == "hxllo" + "123456789".tr("2-23","xy").should == "1xy456789" + "hello ^-^".tr("e-", "a-a_").should == "hallo ^_^" + "hello ^-^".tr("---o", "_a").should == "hella ^_^" end it "pads to_str with its last char if it is shorter than from_string" do diff --git a/spec/ruby/core/string/unpack/b_spec.rb b/spec/ruby/core/string/unpack/b_spec.rb index b088f901fc026c..70ea1cb6ad98e3 100644 --- a/spec/ruby/core/string/unpack/b_spec.rb +++ b/spec/ruby/core/string/unpack/b_spec.rb @@ -86,20 +86,10 @@ ].should be_computed_by(:unpack, "BBB") end - ruby_version_is ""..."3.3" do - it "ignores NULL bytes between directives" do - suppress_warning do - "\x80\x00".unpack("B\x00B").should == ["1", "0"] - end - end - end - - ruby_version_is "3.3" do - it "raise ArgumentError for NULL bytes between directives" do - -> { - "\x80\x00".unpack("B\x00B") - }.should raise_error(ArgumentError, /unknown unpack directive/) - end + it "raise ArgumentError for NULL bytes between directives" do + -> { + "\x80\x00".unpack("B\x00B") + }.should raise_error(ArgumentError, /unknown unpack directive/) end it "ignores spaces between directives" do @@ -194,20 +184,10 @@ ].should be_computed_by(:unpack, "bbb") end - ruby_version_is ""..."3.3" do - it "ignores NULL bytes between directives" do - suppress_warning do - "\x01\x00".unpack("b\x00b").should == ["1", "0"] - end - end - end - - ruby_version_is "3.3" do - it "raise ArgumentError for NULL bytes between directives" do - -> { - "\x01\x00".unpack("b\x00b") - }.should raise_error(ArgumentError, /unknown unpack directive/) - end + it "raise ArgumentError for NULL bytes between directives" do + -> { + "\x01\x00".unpack("b\x00b") + }.should raise_error(ArgumentError, /unknown unpack directive/) end it "ignores spaces between directives" do diff --git a/spec/ruby/core/string/unpack/c_spec.rb b/spec/ruby/core/string/unpack/c_spec.rb index 1e9548fb82411a..e42b027c7b8d6c 100644 --- a/spec/ruby/core/string/unpack/c_spec.rb +++ b/spec/ruby/core/string/unpack/c_spec.rb @@ -35,20 +35,10 @@ ].should be_computed_by(:unpack, unpack_format(3)) end - ruby_version_is ""..."3.3" do - it "ignores NULL bytes between directives" do - suppress_warning do - "abc".unpack(unpack_format("\000", 2)).should == [97, 98] - end - end - end - - ruby_version_is "3.3" do - it "raise ArgumentError for NULL bytes between directives" do - -> { - "abc".unpack(unpack_format("\000", 2)) - }.should raise_error(ArgumentError, /unknown unpack directive/) - end + it "raise ArgumentError for NULL bytes between directives" do + -> { + "abc".unpack(unpack_format("\000", 2)) + }.should raise_error(ArgumentError, /unknown unpack directive/) end it "ignores spaces between directives" do diff --git a/spec/ruby/core/string/unpack/h_spec.rb b/spec/ruby/core/string/unpack/h_spec.rb index 535836087d0f56..130b36401a7d05 100644 --- a/spec/ruby/core/string/unpack/h_spec.rb +++ b/spec/ruby/core/string/unpack/h_spec.rb @@ -56,20 +56,10 @@ ].should be_computed_by(:unpack, "HHH") end - ruby_version_is ""..."3.3" do - it "ignores NULL bytes between directives" do - suppress_warning do - "\x01\x10".unpack("H\x00H").should == ["0", "1"] - end - end - end - - ruby_version_is "3.3" do - it "raise ArgumentError for NULL bytes between directives" do - -> { - "\x01\x10".unpack("H\x00H") - }.should raise_error(ArgumentError, /unknown unpack directive/) - end + it "raise ArgumentError for NULL bytes between directives" do + -> { + "\x01\x10".unpack("H\x00H") + }.should raise_error(ArgumentError, /unknown unpack directive/) end it "ignores spaces between directives" do @@ -133,20 +123,10 @@ ].should be_computed_by(:unpack, "hhh") end - ruby_version_is ""..."3.3" do - it "ignores NULL bytes between directives" do - suppress_warning do - "\x01\x10".unpack("h\x00h").should == ["1", "0"] - end - end - end - - ruby_version_is "3.3" do - it "raise ArgumentError for NULL bytes between directives" do - -> { - "\x01\x10".unpack("h\x00h") - }.should raise_error(ArgumentError, /unknown unpack directive/) - end + it "raise ArgumentError for NULL bytes between directives" do + -> { + "\x01\x10".unpack("h\x00h") + }.should raise_error(ArgumentError, /unknown unpack directive/) end it "ignores spaces between directives" do diff --git a/spec/ruby/core/string/unpack/shared/basic.rb b/spec/ruby/core/string/unpack/shared/basic.rb index 734630bda0a620..132c4ef08acf21 100644 --- a/spec/ruby/core/string/unpack/shared/basic.rb +++ b/spec/ruby/core/string/unpack/shared/basic.rb @@ -9,20 +9,10 @@ "abc".unpack(d).should be_an_instance_of(Array) end - ruby_version_is ""..."3.3" do - it "warns about using an unknown directive" do - -> { "abcdefgh".unpack("a R" + unpack_format) }.should complain(/unknown unpack directive 'R' in 'a R#{unpack_format}'/) - -> { "abcdefgh".unpack("a 0" + unpack_format) }.should complain(/unknown unpack directive '0' in 'a 0#{unpack_format}'/) - -> { "abcdefgh".unpack("a :" + unpack_format) }.should complain(/unknown unpack directive ':' in 'a :#{unpack_format}'/) - end - end - - ruby_version_is "3.3" do - it "raises ArgumentError when a directive is unknown" do - -> { "abcdefgh".unpack("a K" + unpack_format) }.should raise_error(ArgumentError, "unknown unpack directive 'K' in 'a K#{unpack_format}'") - -> { "abcdefgh".unpack("a 0" + unpack_format) }.should raise_error(ArgumentError, "unknown unpack directive '0' in 'a 0#{unpack_format}'") - -> { "abcdefgh".unpack("a :" + unpack_format) }.should raise_error(ArgumentError, "unknown unpack directive ':' in 'a :#{unpack_format}'") - end + it "raises ArgumentError when a directive is unknown" do + -> { "abcdefgh".unpack("a K" + unpack_format) }.should raise_error(ArgumentError, "unknown unpack directive 'K' in 'a K#{unpack_format}'") + -> { "abcdefgh".unpack("a 0" + unpack_format) }.should raise_error(ArgumentError, "unknown unpack directive '0' in 'a 0#{unpack_format}'") + -> { "abcdefgh".unpack("a :" + unpack_format) }.should raise_error(ArgumentError, "unknown unpack directive ':' in 'a :#{unpack_format}'") end end diff --git a/spec/ruby/core/string/unpack/shared/float.rb b/spec/ruby/core/string/unpack/shared/float.rb index b31c2c8bdc406a..0133be2ecb498e 100644 --- a/spec/ruby/core/string/unpack/shared/float.rb +++ b/spec/ruby/core/string/unpack/shared/float.rb @@ -56,21 +56,10 @@ [nan_value].pack(unpack_format).unpack(unpack_format).first.nan?.should be_true end - ruby_version_is ""..."3.3" do - it "ignores NULL bytes between directives" do - suppress_warning do - array = "\x9a\x999@33\xb3?".unpack(unpack_format("\000", 2)) - array.should == [2.9000000953674316, 1.399999976158142] - end - end - end - - ruby_version_is "3.3" do - it "raise ArgumentError for NULL bytes between directives" do - -> { - "\x9a\x999@33\xb3?".unpack(unpack_format("\000", 2)) - }.should raise_error(ArgumentError, /unknown unpack directive/) - end + it "raise ArgumentError for NULL bytes between directives" do + -> { + "\x9a\x999@33\xb3?".unpack(unpack_format("\000", 2)) + }.should raise_error(ArgumentError, /unknown unpack directive/) end it "ignores spaces between directives" do @@ -135,21 +124,10 @@ [nan_value].pack(unpack_format).unpack(unpack_format).first.nan?.should be_true end - ruby_version_is ""..."3.3" do - it "ignores NULL bytes between directives" do - suppress_warning do - array = "@9\x99\x9a?\xb333".unpack(unpack_format("\000", 2)) - array.should == [2.9000000953674316, 1.399999976158142] - end - end - end - - ruby_version_is "3.3" do - it "raise ArgumentError for NULL bytes between directives" do - -> { - "@9\x99\x9a?\xb333".unpack(unpack_format("\000", 2)) - }.should raise_error(ArgumentError, /unknown unpack directive/) - end + it "raise ArgumentError for NULL bytes between directives" do + -> { + "@9\x99\x9a?\xb333".unpack(unpack_format("\000", 2)) + }.should raise_error(ArgumentError, /unknown unpack directive/) end it "ignores spaces between directives" do @@ -217,20 +195,10 @@ [nan_value].pack(unpack_format).unpack(unpack_format).first.nan?.should be_true end - ruby_version_is ""..."3.3" do - it "ignores NULL bytes between directives" do - suppress_warning do - "333333\x07@ffffff\xf6?".unpack(unpack_format("\000", 2)).should == [2.9, 1.4] - end - end - end - - ruby_version_is "3.3" do - it "raise ArgumentError for NULL bytes between directives" do - -> { - "333333\x07@ffffff\xf6?".unpack(unpack_format("\000", 2)) - }.should raise_error(ArgumentError, /unknown unpack directive/) - end + it "raise ArgumentError for NULL bytes between directives" do + -> { + "333333\x07@ffffff\xf6?".unpack(unpack_format("\000", 2)) + }.should raise_error(ArgumentError, /unknown unpack directive/) end it "ignores spaces between directives" do @@ -297,20 +265,10 @@ [nan_value].pack(unpack_format).unpack(unpack_format).first.nan?.should be_true end - ruby_version_is ""..."3.3" do - it "ignores NULL bytes between directives" do - suppress_warning do - "@\x07333333?\xf6ffffff".unpack(unpack_format("\000", 2)).should == [2.9, 1.4] - end - end - end - - ruby_version_is "3.3" do - it "raise ArgumentError for NULL bytes between directives" do - -> { - "@\x07333333?\xf6ffffff".unpack(unpack_format("\000", 2)) - }.should raise_error(ArgumentError, /unknown unpack directive/) - end + it "raise ArgumentError for NULL bytes between directives" do + -> { + "@\x07333333?\xf6ffffff".unpack(unpack_format("\000", 2)) + }.should raise_error(ArgumentError, /unknown unpack directive/) end it "ignores spaces between directives" do diff --git a/spec/ruby/core/string/unpack/shared/integer.rb b/spec/ruby/core/string/unpack/shared/integer.rb index d3934753ba3ef8..eb994562251732 100644 --- a/spec/ruby/core/string/unpack/shared/integer.rb +++ b/spec/ruby/core/string/unpack/shared/integer.rb @@ -32,20 +32,10 @@ ].should be_computed_by(:unpack, unpack_format(3)) end - ruby_version_is ""..."3.3" do - it "ignores NULL bytes between directives" do - suppress_warning do - "abcd".unpack(unpack_format("\000", 2)).should == [25185, 25699] - end - end - end - - ruby_version_is "3.3" do - it "raise ArgumentError for NULL bytes between directives" do - -> { - "abcd".unpack(unpack_format("\000", 2)) - }.should raise_error(ArgumentError, /unknown unpack directive/) - end + it "raise ArgumentError for NULL bytes between directives" do + -> { + "abcd".unpack(unpack_format("\000", 2)) + }.should raise_error(ArgumentError, /unknown unpack directive/) end it "ignores spaces between directives" do @@ -97,20 +87,10 @@ ].should be_computed_by(:unpack, unpack_format(3)) end - ruby_version_is ""..."3.3" do - it "ignores NULL bytes between directives" do - suppress_warning do - "badc".unpack(unpack_format("\000", 2)).should == [25185, 25699] - end - end - end - - ruby_version_is "3.3" do - it "raise ArgumentError for NULL bytes between directives" do - -> { - "badc".unpack(unpack_format("\000", 2)) - }.should raise_error(ArgumentError, /unknown unpack directive/) - end + it "raise ArgumentError for NULL bytes between directives" do + -> { + "badc".unpack(unpack_format("\000", 2)) + }.should raise_error(ArgumentError, /unknown unpack directive/) end it "ignores spaces between directives" do @@ -163,20 +143,10 @@ ].should be_computed_by(:unpack, unpack_format(3)) end - ruby_version_is ""..."3.3" do - it "ignores NULL bytes between directives" do - suppress_warning do - "abcdefgh".unpack(unpack_format("\000", 2)).should == [1684234849, 1751606885] - end - end - end - - ruby_version_is "3.3" do - it "raise ArgumentError for NULL bytes between directives" do - -> { - "abcdefgh".unpack(unpack_format("\000", 2)) - }.should raise_error(ArgumentError, /unknown unpack directive/) - end + it "raise ArgumentError for NULL bytes between directives" do + -> { + "abcdefgh".unpack(unpack_format("\000", 2)) + }.should raise_error(ArgumentError, /unknown unpack directive/) end it "ignores spaces between directives" do @@ -229,20 +199,10 @@ ].should be_computed_by(:unpack, unpack_format(3)) end - ruby_version_is ""..."3.3" do - it "ignores NULL bytes between directives" do - suppress_warning do - "dcbahgfe".unpack(unpack_format("\000", 2)).should == [1684234849, 1751606885] - end - end - end - - ruby_version_is "3.3" do - it "raise ArgumentError for NULL bytes between directives" do - -> { - "dcbahgfe".unpack(unpack_format("\000", 2)) - }.should raise_error(ArgumentError, /unknown unpack directive/) - end + it "raise ArgumentError for NULL bytes between directives" do + -> { + "dcbahgfe".unpack(unpack_format("\000", 2)) + }.should raise_error(ArgumentError, /unknown unpack directive/) end it "ignores spaces between directives" do @@ -291,21 +251,10 @@ "abc".unpack(unpack_format('*')).should == [] end - ruby_version_is ""..."3.3" do - it "ignores NULL bytes between directives" do - suppress_warning do - array = "abcdefghabghefcd".unpack(unpack_format("\000", 2)) - array.should == [7523094288207667809, 7233738012216484449] - end - end - end - - ruby_version_is "3.3" do - it "raise ArgumentError for NULL bytes between directives" do - -> { - "badc".unpack(unpack_format("\000", 2)) - }.should raise_error(ArgumentError, /unknown unpack directive/) - end + it "raise ArgumentError for NULL bytes between directives" do + -> { + "badc".unpack(unpack_format("\000", 2)) + }.should raise_error(ArgumentError, /unknown unpack directive/) end it "ignores spaces between directives" do @@ -365,21 +314,10 @@ "abc".unpack(unpack_format('*')).should == [] end - ruby_version_is ""..."3.3" do - it "ignores NULL bytes between directives" do - suppress_warning do - array = "hgfedcbadcfehgba".unpack(unpack_format("\000", 2)) - array.should == [7523094288207667809, 7233738012216484449] - end - end - end - - ruby_version_is "3.3" do - it "raise ArgumentError for NULL bytes between directives" do - -> { - "hgfedcbadcfehgba".unpack(unpack_format("\000", 2)) - }.should raise_error(ArgumentError, /unknown unpack directive/) - end + it "raise ArgumentError for NULL bytes between directives" do + -> { + "hgfedcbadcfehgba".unpack(unpack_format("\000", 2)) + }.should raise_error(ArgumentError, /unknown unpack directive/) end it "ignores spaces between directives" do diff --git a/spec/ruby/core/string/unpack/shared/unicode.rb b/spec/ruby/core/string/unpack/shared/unicode.rb index 9fe07f53aec1b3..b056aaed0be627 100644 --- a/spec/ruby/core/string/unpack/shared/unicode.rb +++ b/spec/ruby/core/string/unpack/shared/unicode.rb @@ -50,20 +50,10 @@ "\xc2\x80".unpack("UUUU").should == [0x80] end - ruby_version_is ""..."3.3" do - it "ignores NULL bytes between directives" do - suppress_warning do - "\x01\x02".unpack("U\x00U").should == [1, 2] - end - end - end - - ruby_version_is "3.3" do - it "raise ArgumentError for NULL bytes between directives" do - -> { - "\x01\x02".unpack("U\x00U") - }.should raise_error(ArgumentError, /unknown unpack directive/) - end + it "raise ArgumentError for NULL bytes between directives" do + -> { + "\x01\x02".unpack("U\x00U") + }.should raise_error(ArgumentError, /unknown unpack directive/) end it "ignores spaces between directives" do diff --git a/spec/ruby/core/string/unpack/w_spec.rb b/spec/ruby/core/string/unpack/w_spec.rb index 7d3533ccae109e..d2ad657b09c8b8 100644 --- a/spec/ruby/core/string/unpack/w_spec.rb +++ b/spec/ruby/core/string/unpack/w_spec.rb @@ -15,20 +15,10 @@ ].should be_computed_by(:unpack, "w") end - ruby_version_is ""..."3.3" do - it "ignores NULL bytes between directives" do - suppress_warning do - "\x01\x02\x03".unpack("w\x00w").should == [1, 2] - end - end - end - - ruby_version_is "3.3" do - it "raise ArgumentError for NULL bytes between directives" do - -> { - "\x01\x02\x03".unpack("w\x00w") - }.should raise_error(ArgumentError, /unknown unpack directive/) - end + it "raise ArgumentError for NULL bytes between directives" do + -> { + "\x01\x02\x03".unpack("w\x00w") + }.should raise_error(ArgumentError, /unknown unpack directive/) end it "ignores spaces between directives" do diff --git a/spec/ruby/core/struct/new_spec.rb b/spec/ruby/core/struct/new_spec.rb index 1d35de7b871230..741d6889af08a1 100644 --- a/spec/ruby/core/struct/new_spec.rb +++ b/spec/ruby/core/struct/new_spec.rb @@ -77,18 +77,10 @@ def obj.to_str() "Foo" end -> { Struct.new(:animal, { name: 'chris' }) }.should raise_error(TypeError) end - ruby_version_is ""..."3.3" do - it "raises ArgumentError if not provided any arguments" do - -> { Struct.new }.should raise_error(ArgumentError) - end - end - - ruby_version_is "3.3" do - it "works when not provided any arguments" do - c = Struct.new - c.should be_kind_of(Class) - c.superclass.should == Struct - end + it "works when not provided any arguments" do + c = Struct.new + c.should be_kind_of(Class) + c.superclass.should == Struct end it "raises ArgumentError when there is a duplicate member" do diff --git a/spec/ruby/core/symbol/inspect_spec.rb b/spec/ruby/core/symbol/inspect_spec.rb index df4566c48e6449..f2269996af0f92 100644 --- a/spec/ruby/core/symbol/inspect_spec.rb +++ b/spec/ruby/core/symbol/inspect_spec.rb @@ -109,4 +109,23 @@ input.inspect.should == expected end end + + it "quotes BINARY symbols" do + sym = "foo\xA4".b.to_sym + sym.inspect.should == ':"foo\xA4"' + end + + it "quotes symbols in non-ASCII-compatible encodings" do + Encoding.list.reject(&:ascii_compatible?).reject(&:dummy?).each do |encoding| + sym = "foo".encode(encoding).to_sym + sym.inspect.should == ':"foo"' + end + end + + it "quotes and escapes symbols in dummy encodings" do + Encoding.list.select(&:dummy?).each do |encoding| + sym = "abcd".dup.force_encoding(encoding).to_sym + sym.inspect.should == ':"\x61\x62\x63\x64"' + end + end end diff --git a/spec/ruby/core/thread/backtrace/location/fixtures/classes.rb b/spec/ruby/core/thread/backtrace/location/fixtures/classes.rb index e903c3e450fe97..103c36b3a0ab04 100644 --- a/spec/ruby/core/thread/backtrace/location/fixtures/classes.rb +++ b/spec/ruby/core/thread/backtrace/location/fixtures/classes.rb @@ -1,10 +1,26 @@ +# These are top-level def on purpose to test those cases + +def label_top_method = ThreadBacktraceLocationSpecs::LABEL.call + +def self.label_sdef_method_of_main = ThreadBacktraceLocationSpecs::LABEL.call + +class << self + def label_sclass_method_of_main = ThreadBacktraceLocationSpecs::LABEL.call +end + module ThreadBacktraceLocationSpecs MODULE_LOCATION = caller_locations(0) rescue nil + INSTANCE = Object.new.extend(self) + LABEL = -> { caller_locations(1, 1)[0].label } def self.locations caller_locations end + def instance_method_location + caller_locations(0) + end + def self.method_location caller_locations(0) end @@ -15,6 +31,12 @@ def self.block_location end end + def instance_block_location + 1.times do + return caller_locations(0) + end + end + def self.locations_inside_nested_blocks first_level_location = nil second_level_location = nil @@ -32,4 +54,86 @@ def self.locations_inside_nested_blocks [first_level_location, second_level_location, third_level_location] end + + def instance_locations_inside_nested_block + loc = nil + 1.times do + 1.times do + loc = caller_locations(0) + end + end + loc + end + + def original_method = LABEL.call + alias_method :aliased_method, :original_method + + module M + class C + def regular_instance_method = LABEL.call + + def self.sdef_class_method = LABEL.call + + class << self + def sclass_method = LABEL.call + + def block_in_sclass_method + -> { + -> { LABEL.call }.call + }.call + end + end + block_in_sclass_method + end + end + + class M::D + def scoped_method = LABEL.call + + def self.sdef_scoped_method = LABEL.call + + class << self + def sclass_scoped_method = LABEL.call + end + + module ::ThreadBacktraceLocationSpecs + def top = LABEL.call + end + + class ::ThreadBacktraceLocationSpecs::Nested + def top_nested = LABEL.call + + class C + def top_nested_c = LABEL.call + end + end + end + + SOME_OBJECT = Object.new + SOME_OBJECT.instance_exec do + def unknown_def_singleton_method = LABEL.call + + def self.unknown_sdef_singleton_method = LABEL.call + end + + M.module_eval do + def module_eval_method = LABEL.call + + def self.sdef_module_eval_method = LABEL.call + end + + def ThreadBacktraceLocationSpecs.string_class_method = LABEL.call + + module M + def ThreadBacktraceLocationSpecs.nested_class_method = LABEL.call + end + + module M + module_function def mod_function = LABEL.call + end + + expr = self + def expr.sdef_expression = LABEL.call + + def expr.block_in_sdef_expression = -> { LABEL.call }.call end diff --git a/spec/ruby/core/thread/backtrace/location/label_spec.rb b/spec/ruby/core/thread/backtrace/location/label_spec.rb index 85ddccc8e3f831..7d358b45ea8fe3 100644 --- a/spec/ruby/core/thread/backtrace/location/label_spec.rb +++ b/spec/ruby/core/thread/backtrace/location/label_spec.rb @@ -15,7 +15,7 @@ end it 'returns the module name for a module location' do - ThreadBacktraceLocationSpecs::MODULE_LOCATION[0].label.should include "ThreadBacktraceLocationSpecs" + ThreadBacktraceLocationSpecs::MODULE_LOCATION[0].label.should == "" end it 'includes the nesting level of a block as part of the location label' do @@ -34,4 +34,194 @@ main_label.should == "block in
\n" required_label.should == "block in \n" end + + it "return the same name as the caller for eval" do + this = caller_locations(0)[0].label + eval("caller_locations(0)[0]").label.should == this + + b = binding + b.eval("caller_locations(0)[0]").label.should == this + + b.local_variable_set(:binding_var1, 1) + b.eval("caller_locations(0)[0]").label.should == this + + b.local_variable_set(:binding_var2, 2) + b.eval("caller_locations(0)[0]").label.should == this + + b.local_variable_set(:binding_var2, 2) + eval("caller_locations(0)[0]", b).label.should == this + end + + ruby_version_is "3.4" do + describe "is Module#method for" do + it "a core method defined natively" do + BasicObject.instance_method(:instance_exec).should_not.source_location + loc = nil + loc = instance_exec { caller_locations(1, 1)[0] } + loc.label.should == "BasicObject#instance_exec" + end + + it "a core method defined in Ruby" do + Kernel.instance_method(:tap).should.source_location + loc = nil + tap { loc = caller_locations(1, 1)[0] } + loc.label.should == "Kernel#tap" + end + + it "an instance method defined in Ruby" do + ThreadBacktraceLocationSpecs::INSTANCE.instance_method_location[0].label.should == "ThreadBacktraceLocationSpecs#instance_method_location" + end + + it "a block in an instance method defined in Ruby" do + ThreadBacktraceLocationSpecs::INSTANCE.instance_block_location[0].label.should == "block in ThreadBacktraceLocationSpecs#instance_block_location" + end + + it "a nested block in an instance method defined in Ruby" do + ThreadBacktraceLocationSpecs::INSTANCE.instance_locations_inside_nested_block[0].label.should == "block (2 levels) in ThreadBacktraceLocationSpecs#instance_locations_inside_nested_block" + end + + it "a method defined via module_exec" do + ThreadBacktraceLocationSpecs.module_exec do + def in_module_exec + caller_locations(0) + end + end + ThreadBacktraceLocationSpecs::INSTANCE.in_module_exec[0].label.should == "ThreadBacktraceLocationSpecs#in_module_exec" + end + + it "a method defined via module_eval" do + ThreadBacktraceLocationSpecs.module_eval <<~RUBY + def in_module_eval + caller_locations(0) + end + RUBY + ThreadBacktraceLocationSpecs::INSTANCE.in_module_eval[0].label.should == "ThreadBacktraceLocationSpecs#in_module_eval" + end + end + + describe "is Module.method for" do + it "a singleton method defined in Ruby" do + ThreadBacktraceLocationSpecs.method_location[0].label.should == "ThreadBacktraceLocationSpecs.method_location" + end + + it "a block in a singleton method defined in Ruby" do + ThreadBacktraceLocationSpecs.block_location[0].label.should == "block in ThreadBacktraceLocationSpecs.block_location" + end + + it "a nested block in a singleton method defined in Ruby" do + ThreadBacktraceLocationSpecs.locations_inside_nested_blocks[2].label.should == "block (3 levels) in ThreadBacktraceLocationSpecs.locations_inside_nested_blocks" + end + + it "a singleton method defined via def Const.method" do + def ThreadBacktraceLocationSpecs.def_singleton + caller_locations(0) + end + ThreadBacktraceLocationSpecs.def_singleton[0].label.should == "ThreadBacktraceLocationSpecs.def_singleton" + end + end + + it "shows the original method name for an aliased method" do + ThreadBacktraceLocationSpecs::INSTANCE.aliased_method.should == "ThreadBacktraceLocationSpecs#original_method" + end + + # A wide variety of cases. + # These show interesting cases when trying to determine the name statically/at parse time + describe "is correct for" do + base = ThreadBacktraceLocationSpecs + + it "M::C#regular_instance_method" do + base::M::C.new.regular_instance_method.should == "#{base}::M::C#regular_instance_method" + end + + it "M::C.sdef_class_method" do + base::M::C.sdef_class_method.should == "#{base}::M::C.sdef_class_method" + end + + it "M::C.sclass_method" do + base::M::C.sclass_method.should == "#{base}::M::C.sclass_method" + end + + it "M::C.block_in_sclass_method" do + base::M::C.block_in_sclass_method.should == "block (2 levels) in #{base}::M::C.block_in_sclass_method" + end + + it "M::D#scoped_method" do + base::M::D.new.scoped_method.should == "#{base}::M::D#scoped_method" + end + + it "M::D.sdef_scoped_method" do + base::M::D.sdef_scoped_method.should == "#{base}::M::D.sdef_scoped_method" + end + + it "M::D.sclass_scoped_method" do + base::M::D.sclass_scoped_method.should == "#{base}::M::D.sclass_scoped_method" + end + + it "ThreadBacktraceLocationSpecs#top" do + ThreadBacktraceLocationSpecs::INSTANCE.top.should == "ThreadBacktraceLocationSpecs#top" + end + + it "ThreadBacktraceLocationSpecs::Nested#top_nested" do + ThreadBacktraceLocationSpecs::Nested.new.top_nested.should == "ThreadBacktraceLocationSpecs::Nested#top_nested" + end + + it "ThreadBacktraceLocationSpecs::Nested::C#top_nested_c" do + ThreadBacktraceLocationSpecs::Nested::C.new.top_nested_c.should == "ThreadBacktraceLocationSpecs::Nested::C#top_nested_c" + end + + it "Object#label_top_method" do + label_top_method.should == "Object#label_top_method" + end + + it "main.label_sdef_method_of_main" do + main = TOPLEVEL_BINDING.receiver + main.label_sdef_method_of_main.should == "label_sdef_method_of_main" + end + + it "main.label_sclass_method_of_main" do + main = TOPLEVEL_BINDING.receiver + main.label_sclass_method_of_main.should == "label_sclass_method_of_main" + end + + it "unknown_def_singleton_method" do + base::SOME_OBJECT.unknown_def_singleton_method.should == "unknown_def_singleton_method" + end + + it "unknown_sdef_singleton_method" do + base::SOME_OBJECT.unknown_sdef_singleton_method.should == "unknown_sdef_singleton_method" + end + + it "M#module_eval_method" do + Object.new.extend(base::M).module_eval_method.should == "#{base}::M#module_eval_method" + end + + it "M.sdef_module_eval_method" do + base::M.sdef_module_eval_method.should == "#{base}::M.sdef_module_eval_method" + end + + it "ThreadBacktraceLocationSpecs.string_class_method" do + ThreadBacktraceLocationSpecs.string_class_method.should == "ThreadBacktraceLocationSpecs.string_class_method" + end + + it "ThreadBacktraceLocationSpecs.nested_class_method" do + ThreadBacktraceLocationSpecs.nested_class_method.should == "ThreadBacktraceLocationSpecs.nested_class_method" + end + + it "M#mod_function" do + Object.new.extend(base::M).send(:mod_function).should == "#{base}::M#mod_function" + end + + it "M.mod_function" do + base::M.mod_function.should == "#{base}::M.mod_function" + end + + it "sdef_expression" do + base.sdef_expression.should == "#{base}.sdef_expression" + end + + it "block_in_sdef_expression" do + base.block_in_sdef_expression.should == "block in #{base}.block_in_sdef_expression" + end + end + end end diff --git a/spec/ruby/core/thread/native_thread_id_spec.rb b/spec/ruby/core/thread/native_thread_id_spec.rb index 374cc592797a20..65d1b5b318dbac 100644 --- a/spec/ruby/core/thread/native_thread_id_spec.rb +++ b/spec/ruby/core/thread/native_thread_id_spec.rb @@ -18,12 +18,8 @@ main_thread_id = Thread.current.native_thread_id t_thread_id = t.native_thread_id - if ruby_version_is "3.3" - # native_thread_id can be nil on a M:N scheduler - t_thread_id.should be_kind_of(Integer) if t_thread_id != nil - else - t_thread_id.should be_kind_of(Integer) - end + # native_thread_id can be nil on a M:N scheduler + t_thread_id.should be_kind_of(Integer) if t_thread_id != nil main_thread_id.should_not == t_thread_id diff --git a/spec/ruby/core/time/new_spec.rb b/spec/ruby/core/time/new_spec.rb index dc3ccbdc0052df..f3b5d0142044b4 100644 --- a/spec/ruby/core/time/new_spec.rb +++ b/spec/ruby/core/time/new_spec.rb @@ -554,20 +554,10 @@ def obj.to_int; 3; end Time.new("2020-12-25T00:56:17.123456789876 +09:00").subsec.should == 0.123456789 end - ruby_version_is ""..."3.3" do - it "raise TypeError is can't convert precision keyword argument into Integer" do - -> { - Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: "") - }.should raise_error(TypeError, "no implicit conversion from string") - end - end - - ruby_version_is "3.3" do - it "raise TypeError is can't convert precision keyword argument into Integer" do - -> { - Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: "") - }.should raise_error(TypeError, "no implicit conversion of String into Integer") - end + it "raise TypeError is can't convert precision keyword argument into Integer" do + -> { + Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: "") + }.should raise_error(TypeError, "no implicit conversion of String into Integer") end it "raises ArgumentError if part of time string is missing" do diff --git a/spec/ruby/core/tracepoint/path_spec.rb b/spec/ruby/core/tracepoint/path_spec.rb index dc2ca840b80ca5..aa6868ead2ffd8 100644 --- a/spec/ruby/core/tracepoint/path_spec.rb +++ b/spec/ruby/core/tracepoint/path_spec.rb @@ -13,29 +13,14 @@ path.should == "#{__FILE__}" end - ruby_version_is ""..."3.3" do - it 'equals (eval) inside an eval for :end event' do - path = nil - TracePoint.new(:end) { |tp| - next unless TracePointSpec.target_thread? - path = tp.path - }.enable do - eval("module TracePointSpec; end") - end - path.should == '(eval)' - end - end - - ruby_version_is "3.3" do - it 'equals "(eval at __FILE__:__LINE__)" inside an eval for :end event' do - path = nil - TracePoint.new(:end) { |tp| - next unless TracePointSpec.target_thread? - path = tp.path - }.enable do - eval("module TracePointSpec; end") - end - path.should == "(eval at #{__FILE__}:#{__LINE__ - 2})" + it 'equals "(eval at __FILE__:__LINE__)" inside an eval for :end event' do + path = nil + TracePoint.new(:end) { |tp| + next unless TracePointSpec.target_thread? + path = tp.path + }.enable do + eval("module TracePointSpec; end") end + path.should == "(eval at #{__FILE__}:#{__LINE__ - 2})" end end diff --git a/spec/ruby/core/tracepoint/raised_exception_spec.rb b/spec/ruby/core/tracepoint/raised_exception_spec.rb index 5ac85318404964..e74afa9abc96c1 100644 --- a/spec/ruby/core/tracepoint/raised_exception_spec.rb +++ b/spec/ruby/core/tracepoint/raised_exception_spec.rb @@ -18,21 +18,19 @@ end end - ruby_version_is "3.3" do - it 'returns value from exception rescued on the :rescue event' do - raised_exception, error_result = nil - trace = TracePoint.new(:rescue) { |tp| - next unless TracePointSpec.target_thread? - raised_exception = tp.raised_exception - } - trace.enable do - begin - raise StandardError - rescue => e - error_result = e - end - raised_exception.should equal(error_result) + it 'returns value from exception rescued on the :rescue event' do + raised_exception, error_result = nil + trace = TracePoint.new(:rescue) { |tp| + next unless TracePointSpec.target_thread? + raised_exception = tp.raised_exception + } + trace.enable do + begin + raise StandardError + rescue => e + error_result = e end + raised_exception.should equal(error_result) end end end diff --git a/spec/ruby/core/true/singleton_method_spec.rb b/spec/ruby/core/true/singleton_method_spec.rb index c06793850fa87a..575c504b728da3 100644 --- a/spec/ruby/core/true/singleton_method_spec.rb +++ b/spec/ruby/core/true/singleton_method_spec.rb @@ -1,15 +1,13 @@ require_relative '../../spec_helper' describe "TrueClass#singleton_method" do - ruby_version_is '3.3' do - it "raises regardless of whether TrueClass defines the method" do + it "raises regardless of whether TrueClass defines the method" do + -> { true.singleton_method(:foo) }.should raise_error(NameError) + begin + def (true).foo; end -> { true.singleton_method(:foo) }.should raise_error(NameError) - begin - def (true).foo; end - -> { true.singleton_method(:foo) }.should raise_error(NameError) - ensure - TrueClass.send(:remove_method, :foo) - end + ensure + TrueClass.send(:remove_method, :foo) end end end diff --git a/spec/ruby/core/unboundmethod/equal_value_spec.rb b/spec/ruby/core/unboundmethod/equal_value_spec.rb index b2d78c50afb359..c9f7ad45dacc82 100644 --- a/spec/ruby/core/unboundmethod/equal_value_spec.rb +++ b/spec/ruby/core/unboundmethod/equal_value_spec.rb @@ -110,9 +110,6 @@ class << self c.method(:n).should == Class.instance_method(:new).bind(c) end - # On CRuby < 3.2, the 2 specs below pass due to method/instance_method skipping zsuper methods. - # We are interested in the general pattern working, i.e. the combination of method/instance_method - # and #== exposes the wanted behavior. it "considers methods through visibility change equal" do c = Class.new do class << self diff --git a/spec/ruby/core/warning/element_reference_spec.rb b/spec/ruby/core/warning/element_reference_spec.rb index c0ed37ef139d05..6179c578646255 100644 --- a/spec/ruby/core/warning/element_reference_spec.rb +++ b/spec/ruby/core/warning/element_reference_spec.rb @@ -10,11 +10,9 @@ ruby_exe('p [Warning[:deprecated], Warning[:experimental]]', options: "-w").chomp.should == "[true, true]" end - ruby_version_is '3.3' do - it "returns default values for :performance category" do - ruby_exe('p Warning[:performance]').chomp.should == "false" - ruby_exe('p Warning[:performance]', options: "-w").chomp.should == "false" - end + it "returns default values for :performance category" do + ruby_exe('p Warning[:performance]').chomp.should == "false" + ruby_exe('p Warning[:performance]', options: "-w").chomp.should == "false" end it "raises for unknown category" do diff --git a/spec/ruby/core/warning/element_set_spec.rb b/spec/ruby/core/warning/element_set_spec.rb index d59a7d4c9e13c8..1dbc66ce26cae9 100644 --- a/spec/ruby/core/warning/element_set_spec.rb +++ b/spec/ruby/core/warning/element_set_spec.rb @@ -17,15 +17,13 @@ end end - ruby_version_is '3.3' do - it "enables or disables performance warnings" do - original = Warning[:performance] - begin - Warning[:performance] = !original - Warning[:performance].should == !original - ensure - Warning[:performance] = original - end + it "enables or disables performance warnings" do + original = Warning[:performance] + begin + Warning[:performance] = !original + Warning[:performance].should == !original + ensure + Warning[:performance] = original end end diff --git a/spec/ruby/language/assignments_spec.rb b/spec/ruby/language/assignments_spec.rb index c4adf73c1cbf67..58a244b7c27d87 100644 --- a/spec/ruby/language/assignments_spec.rb +++ b/spec/ruby/language/assignments_spec.rb @@ -219,15 +219,7 @@ def []=(*args, **kw) end end - ruby_version_is ""..."3.3" do - it "supports keyword arguments in index assignments" do - a = @klass.new - eval "a[1, 2, 3, b: 4] += 5" - a.x.should == [[1, 2, 3, {b: 4}, 105], {}] - end - end - - ruby_version_is "3.3"..."3.4" do + ruby_version_is ""..."3.4" do it "supports keyword arguments in index assignments" do a = @klass.new eval "a[1, 2, 3, b: 4] += 5" diff --git a/spec/ruby/language/block_spec.rb b/spec/ruby/language/block_spec.rb index cc003b8946270e..67aad76c57e922 100644 --- a/spec/ruby/language/block_spec.rb +++ b/spec/ruby/language/block_spec.rb @@ -192,6 +192,22 @@ def m(a) yield a end m(obj) { |a, b, c| [a, b, c] }.should == [1, 2, nil] end + it "calls #respond_to? on a BasicObject to check if object has method #to_ary" do + ScratchPad.record [] + obj = BasicObject.new + def obj.respond_to?(name, *) + ScratchPad << [:respond_to?, name] + name == :to_ary ? true : super + end + def obj.to_ary + ScratchPad << :to_ary + [1, 2] + end + + m(obj) { |a, b, c| [a, b, c] }.should == [1, 2, nil] + ScratchPad.recorded.should == [[:respond_to?, :to_ary], :to_ary] + end + it "receives the object if it does not respond to #respond_to?" do obj = BasicObject.new @@ -1041,8 +1057,8 @@ def all_kwrest(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, ** end end -describe "`it` calls without arguments in a block with no ordinary parameters" do - ruby_version_is "3.3"..."3.4" do +describe "`it` calls without arguments in a block" do + ruby_version_is ""..."3.4" do it "emits a deprecation warning" do -> { eval "proc { it }" @@ -1094,38 +1110,11 @@ def o.it end end end - - ruby_version_is "3.4" do - it "does not emit a deprecation warning" do - -> { - eval "proc { it }" - }.should_not complain - end - - it "acts as the first argument if no local variables exist" do - eval("proc { it * 2 }").call(5).should == 10 - end - - it "can be reassigned to act as a local variable" do - eval("proc { tmp = it; it = tmp * 2; it }").call(21).should == 42 - end - - it "can be used in nested calls" do - eval("proc { it.map { it * 2 } }").call([1, 2, 3]).should == [2, 4, 6] - end - - it "cannot be mixed with numbered parameters" do - -> { - eval "proc { it + _1 }" - }.should raise_error(SyntaxError, /numbered parameters are not allowed when 'it' is already used|'it' is already used in/) - - -> { - eval "proc { _1 + it }" - }.should raise_error(SyntaxError, /numbered parameter is already used in|'it' is not allowed when a numbered parameter is already used/) - end - end end +# Duplicates specs in language/it_parameter_spec.rb +# Need them here to run on Ruby versions prior 3.4 +# TODO: remove when the minimal supported Ruby version is 3.4 describe "if `it` is defined as a variable" do it "treats `it` as a captured variable if defined outside of a block" do it = 5 diff --git a/spec/ruby/language/delegation_spec.rb b/spec/ruby/language/delegation_spec.rb index c711a536c22d71..cd44956f5d1c65 100644 --- a/spec/ruby/language/delegation_spec.rb +++ b/spec/ruby/language/delegation_spec.rb @@ -37,6 +37,16 @@ def delegate(...) a.new.delegate(1, b: 2, &block).should == [[1], {b: 2}, block] end + it "delegates with additional arguments" do + a = Class.new(DelegationSpecs::Target) + a.class_eval(<<-RUBY) + def delegate(...) + target(:first, :second, ...) + end + RUBY + a.new.delegate(1, b: 2).should == [[:first, :second, 1], {b: 2}, nil] + end + it "parses as open endless Range when brackets are omitted" do a = Class.new(DelegationSpecs::Target) suppress_warning do @@ -99,13 +109,11 @@ def delegate(*) a.new.delegate(0, 1).should == [[0, 1], {}, nil] end - ruby_version_is "3.3" do - context "within a block that accepts anonymous rest within a method that accepts anonymous rest" do - it "does not allow delegating rest" do - -> { - eval "def m(*); proc { |*| n(*) } end" - }.should raise_error(SyntaxError, /anonymous rest parameter is also used within block/) - end + context "within a block that accepts anonymous rest within a method that accepts anonymous rest" do + it "does not allow delegating rest" do + -> { + eval "def m(*); proc { |*| n(*) } end" + }.should raise_error(SyntaxError, /anonymous rest parameter is also used within block/) end end end @@ -122,13 +130,11 @@ def delegate(**) a.new.delegate(a: 1) { |x| x }.should == [[], {a: 1}, nil] end - ruby_version_is "3.3" do - context "within a block that accepts anonymous kwargs within a method that accepts anonymous kwargs" do - it "does not allow delegating kwargs" do - -> { - eval "def m(**); proc { |**| n(**) } end" - }.should raise_error(SyntaxError, /anonymous keyword rest parameter is also used within block/) - end + context "within a block that accepts anonymous kwargs within a method that accepts anonymous kwargs" do + it "does not allow delegating kwargs" do + -> { + eval "def m(**); proc { |**| n(**) } end" + }.should raise_error(SyntaxError, /anonymous keyword rest parameter is also used within block/) end end end @@ -146,13 +152,11 @@ def delegate(&) a.new.delegate(&block).should == [[], {}, block] end - ruby_version_is "3.3" do - context "within a block that accepts anonymous block within a method that accepts anonymous block" do - it "does not allow delegating a block" do - -> { - eval "def m(&); proc { |&| n(&) } end" - }.should raise_error(SyntaxError, /anonymous block parameter is also used within block/) - end + context "within a block that accepts anonymous block within a method that accepts anonymous block" do + it "does not allow delegating a block" do + -> { + eval "def m(&); proc { |&| n(&) } end" + }.should raise_error(SyntaxError, /anonymous block parameter is also used within block/) end end end diff --git a/spec/ruby/language/file_spec.rb b/spec/ruby/language/file_spec.rb index 59563d9642e00e..36fd329bf6a7ca 100644 --- a/spec/ruby/language/file_spec.rb +++ b/spec/ruby/language/file_spec.rb @@ -7,16 +7,8 @@ -> { eval("__FILE__ = 1") }.should raise_error(SyntaxError) end - ruby_version_is ""..."3.3" do - it "equals (eval) inside an eval" do - eval("__FILE__").should == "(eval)" - end - end - - ruby_version_is "3.3" do - it "equals (eval at __FILE__:__LINE__) inside an eval" do - eval("__FILE__").should == "(eval at #{__FILE__}:#{__LINE__})" - end + it "equals (eval at __FILE__:__LINE__) inside an eval" do + eval("__FILE__").should == "(eval at #{__FILE__}:#{__LINE__})" end end diff --git a/spec/ruby/language/for_spec.rb b/spec/ruby/language/for_spec.rb index b8ddfe5f0ddfb0..7fc6751d070eb1 100644 --- a/spec/ruby/language/for_spec.rb +++ b/spec/ruby/language/for_spec.rb @@ -129,37 +129,34 @@ class OFor n.should == 3 end - # Segfault in MRI 3.3 and lower: https://bugs.ruby-lang.org/issues/20468 - ruby_bug "#20468", ""..."3.4" do - it "allows an attribute with safe navigation as an iterator name" do - class OFor - attr_accessor :target - end - - ofor = OFor.new - m = [1,2,3] - n = 0 - eval <<~RUBY - for ofor&.target in m - n += 1 - end - RUBY - ofor.target.should == 3 - n.should == 3 + it "allows an attribute with safe navigation as an iterator name" do + class OFor + attr_accessor :target end - it "allows an attribute with safe navigation on a nil base as an iterator name" do - ofor = nil - m = [1,2,3] - n = 0 - eval <<~RUBY - for ofor&.target in m - n += 1 - end - RUBY - ofor.should be_nil - n.should == 3 - end + ofor = OFor.new + m = [1,2,3] + n = 0 + eval <<~RUBY + for ofor&.target in m + n += 1 + end + RUBY + ofor.target.should == 3 + n.should == 3 + end + + it "allows an attribute with safe navigation on a nil base as an iterator name" do + ofor = nil + m = [1,2,3] + n = 0 + eval <<~RUBY + for ofor&.target in m + n += 1 + end + RUBY + ofor.should be_nil + n.should == 3 end it "allows an array index writer as an iterator name" do diff --git a/spec/ruby/language/hash_spec.rb b/spec/ruby/language/hash_spec.rb index 668716e2e325da..c7e1bf2d88bffd 100644 --- a/spec/ruby/language/hash_spec.rb +++ b/spec/ruby/language/hash_spec.rb @@ -167,6 +167,17 @@ def h.to_hash; {:b => 2, :c => 3}; end {**nil}.should == {} {a: 1, **nil}.should == {a: 1} end + + it "expands nil using ** into {} and provides a copy to the callable" do + ScratchPad.record [] + insert = -> key, **kw do + kw[key] = 1 + ScratchPad << kw + end + insert.call(:foo, **nil) + insert.call(:bar, **nil) + ScratchPad.recorded.should == [{ foo: 1 }, { bar: 1 }] + end end it "expands an '**{}' or '**obj' element with the last key/value pair taking precedence" do @@ -264,17 +275,15 @@ def m(**h) h.should == { one: 1, two: 2 } end - ruby_bug "#20012", ""..."3.3" do - it "makes a copy when calling a method taking a positional Hash" do - def m(h) - h.delete(:one); h - end - - h = { one: 1, two: 2 } - m(**h).should == { two: 2 } - m(**h).should_not.equal?(h) - h.should == { one: 1, two: 2 } + it "makes a copy when calling a method taking a positional Hash" do + def m(h) + h.delete(:one); h end + + h = { one: 1, two: 2 } + m(**h).should == { two: 2 } + m(**h).should_not.equal?(h) + h.should == { one: 1, two: 2 } end describe "hash with omitted value" do diff --git a/spec/ruby/language/it_parameter_spec.rb b/spec/ruby/language/it_parameter_spec.rb index 72023180d91d54..58ec3a6faf0f1a 100644 --- a/spec/ruby/language/it_parameter_spec.rb +++ b/spec/ruby/language/it_parameter_spec.rb @@ -1,6 +1,7 @@ require_relative '../spec_helper' ruby_version_is "3.4" do + eval <<-RUBY # use eval to avoid warnings on Ruby 3.3 describe "The `it` parameter" do it "provides it in a block" do -> { it }.call("a").should == "a" @@ -17,9 +18,28 @@ -> { it + -> { it * it }.call(2) }.call(3).should == 7 end + it "can be reassigned to act as a local variable" do + proc { tmp = it; it = tmp * 2; it }.call(21).should == 42 + end + it "is a regular local variable if there is already a 'it' local variable" do - it = 0 - proc { it }.call("a").should == 0 + it = 0 + proc { it }.call("a").should == 0 + end + + it "is a regular local variable if there is a method `it` defined" do + o = Object.new + def o.it + 21 + end + + o.instance_eval("proc { it * 2 }").call(1).should == 2 + end + + it "is not shadowed by an reassignment in a block" do + a = nil + proc { a = it; it = 42 }.call(0) + a.should == 0 # if `it` were shadowed its value would be nil end it "raises SyntaxError when block parameters are specified explicitly" do @@ -36,6 +56,16 @@ -> { eval("['a'].map { |x| it }") }.should raise_error(SyntaxError, /ordinary parameter is defined/) end + it "cannot be mixed with numbered parameters" do + -> { + eval("proc { it + _1 }") + }.should raise_error(SyntaxError, /numbered parameters are not allowed when 'it' is already used|'it' is already used in/) + + -> { + eval("proc { _1 + it }") + }.should raise_error(SyntaxError, /numbered parameter is already used in|'it' is not allowed when a numbered parameter is already used/) + end + it "affects block arity" do -> {}.arity.should == 0 -> { it }.arity.should == 1 @@ -62,5 +92,17 @@ def obj.foo; it; end -> { obj.foo("a") }.should raise_error(ArgumentError, /wrong number of arguments/) end + + context "given multiple arguments" do + it "provides it in a block and assigns the first argument for a block" do + proc { it }.call("a", "b").should == "a" + end + + it "raises ArgumentError for a proc" do + -> { -> { it }.call("a", "b") }.should raise_error(ArgumentError, "wrong number of arguments (given 2, expected 1)") + -> { lambda { it }.call("a", "b") }.should raise_error(ArgumentError, "wrong number of arguments (given 2, expected 1)") + end + end end + RUBY end diff --git a/spec/ruby/language/keyword_arguments_spec.rb b/spec/ruby/language/keyword_arguments_spec.rb index 4f6370d419e03b..c51c3bc656d4e9 100644 --- a/spec/ruby/language/keyword_arguments_spec.rb +++ b/spec/ruby/language/keyword_arguments_spec.rb @@ -87,16 +87,14 @@ def m(*a) end context "**" do - ruby_version_is "3.3" do - it "copies a non-empty Hash for a method taking (*args)" do - def m(*args) - args[0] - end - - h = {a: 1} - m(**h).should_not.equal?(h) - h.should == {a: 1} + it "copies a non-empty Hash for a method taking (*args)" do + def m(*args) + args[0] end + + h = {a: 1} + m(**h).should_not.equal?(h) + h.should == {a: 1} end it "copies the given Hash for a method taking (**kwargs)" do diff --git a/spec/ruby/language/method_spec.rb b/spec/ruby/language/method_spec.rb index 8f72bd45ed8cbe..8f9f094fd89a45 100644 --- a/spec/ruby/language/method_spec.rb +++ b/spec/ruby/language/method_spec.rb @@ -1234,10 +1234,8 @@ def n(value, &block) args.should == [true] end - ruby_version_is "3.3" do - it "supports multiple statements" do - eval("m (1; 2)").should == [2] - end + it "supports multiple statements" do + eval("m (1; 2)").should == [2] end end diff --git a/spec/ruby/library/English/English_spec.rb b/spec/ruby/library/English/English_spec.rb index 4d615d1e2506ef..166785f066640b 100644 --- a/spec/ruby/library/English/English_spec.rb +++ b/spec/ruby/library/English/English_spec.rb @@ -130,18 +130,6 @@ $LAST_MATCH_INFO.should == $~ end - ruby_version_is ""..."3.3" do - it "aliases $IGNORECASE to $=" do - $VERBOSE, verbose = nil, $VERBOSE - begin - $IGNORECASE.should_not be_nil - $IGNORECASE.should == $= - ensure - $VERBOSE = verbose - end - end - end - it "aliases $ARGV to $*" do $ARGV.should_not be_nil $ARGV.should == $* diff --git a/spec/ruby/library/bigdecimal/remainder_spec.rb b/spec/ruby/library/bigdecimal/remainder_spec.rb index 0eb06f7ef1d402..b31967e76bd53c 100644 --- a/spec/ruby/library/bigdecimal/remainder_spec.rb +++ b/spec/ruby/library/bigdecimal/remainder_spec.rb @@ -56,25 +56,6 @@ @nan.remainder(@infinity).should.nan? end - version_is BigDecimal::VERSION, ""..."3.1.4" do #ruby_version_is ""..."3.3" do - it "returns NaN if Infinity is involved" do - @infinity.remainder(@infinity).should.nan? - @infinity.remainder(@one).should.nan? - @infinity.remainder(@mixed).should.nan? - @infinity.remainder(@one_minus).should.nan? - @infinity.remainder(@frac_1).should.nan? - @one.remainder(@infinity).should.nan? - - @infinity_minus.remainder(@infinity_minus).should.nan? - @infinity_minus.remainder(@one).should.nan? - @one.remainder(@infinity_minus).should.nan? - @frac_2.remainder(@infinity_minus).should.nan? - - @infinity.remainder(@infinity_minus).should.nan? - @infinity_minus.remainder(@infinity).should.nan? - end - end - it "coerces arguments to BigDecimal if possible" do @three.remainder(2).should == @one end diff --git a/spec/ruby/library/bigdecimal/to_s_spec.rb b/spec/ruby/library/bigdecimal/to_s_spec.rb index ba9f960eb32450..025057b4d7e873 100644 --- a/spec/ruby/library/bigdecimal/to_s_spec.rb +++ b/spec/ruby/library/bigdecimal/to_s_spec.rb @@ -52,10 +52,8 @@ BigDecimal("1.2345").to_s('0F').should == "1.2345" end - version_is BigDecimal::VERSION, "3.1.5" do #ruby_version_is '3.3' do - it "inserts a space every n chars to integer part, if integer n is supplied" do - BigDecimal('1000010').to_s('5F').should == "10 00010.0" - end + it "inserts a space every n chars to integer part, if integer n is supplied" do + BigDecimal('1000010').to_s('5F').should == "10 00010.0" end it "can return a leading space for values > 0" do diff --git a/spec/ruby/library/random/formatter/alphanumeric_spec.rb b/spec/ruby/library/random/formatter/alphanumeric_spec.rb index 9bd325e1d0a6ab..ce45b96dc2b7a7 100644 --- a/spec/ruby/library/random/formatter/alphanumeric_spec.rb +++ b/spec/ruby/library/random/formatter/alphanumeric_spec.rb @@ -41,16 +41,14 @@ }.should raise_error(ArgumentError) end - ruby_version_is "3.3" do - it "accepts a 'chars' argument with the output alphabet" do - @object.alphanumeric(chars: ['a', 'b']).should =~ /\A[ab]+\z/ - end + it "accepts a 'chars' argument with the output alphabet" do + @object.alphanumeric(chars: ['a', 'b']).should =~ /\A[ab]+\z/ + end - it "converts the elements of chars using #to_s" do - to_s = mock("to_s") - to_s.should_receive(:to_s).and_return("[mock to_s]") - # Using 1 value in chars results in an infinite loop - @object.alphanumeric(1, chars: [to_s, to_s]).should == "[mock to_s]" - end + it "converts the elements of chars using #to_s" do + to_s = mock("to_s") + to_s.should_receive(:to_s).and_return("[mock to_s]") + # Using 1 value in chars results in an infinite loop + @object.alphanumeric(1, chars: [to_s, to_s]).should == "[mock to_s]" end end diff --git a/spec/ruby/library/ripper/lex_spec.rb b/spec/ruby/library/ripper/lex_spec.rb index 97cfb06904fad6..0255480579ee4f 100644 --- a/spec/ruby/library/ripper/lex_spec.rb +++ b/spec/ruby/library/ripper/lex_spec.rb @@ -10,14 +10,14 @@ [[1, 5], :on_lparen, "(", 'BEG|LABEL'], [[1, 6], :on_ident, "a", 'ARG'], [[1, 7], :on_rparen, ")", 'ENDFN'], - [[1, 8], :on_sp, " ", 'BEG'], + [[1, 8], :on_semicolon, ";", 'BEG'], [[1, 9], :on_kw, "nil", 'END'], [[1, 12], :on_sp, " ", 'END'], [[1, 13], :on_kw, "end", 'END'] ] - lexed = Ripper.lex("def m(a) nil end") + lexed = Ripper.lex("def m(a);nil end") lexed.map { |e| - e[0...-1] + [e[-1].to_s.split('|').map { |s| s.sub(/^EXPR_/, '') }.join('|')] + e[0...-1] + [e[-1].to_s] }.should == expected end end diff --git a/spec/ruby/library/socket/addrinfo/initialize_spec.rb b/spec/ruby/library/socket/addrinfo/initialize_spec.rb index 1f16531aaa4dea..c556bd758b925a 100644 --- a/spec/ruby/library/socket/addrinfo/initialize_spec.rb +++ b/spec/ruby/library/socket/addrinfo/initialize_spec.rb @@ -53,11 +53,11 @@ @addrinfo.ip_port.should == 25 end - it "returns the INET6 pfamily" do + it "returns the specified family" do @addrinfo.pfamily.should == Socket::PF_INET6 end - it "returns the INET6 afamily" do + it "returns the specified family" do @addrinfo.afamily.should == Socket::AF_INET6 end @@ -83,11 +83,11 @@ @addrinfo.ip_port.should == 25 end - it "returns the INET6 pfamily" do + it "returns the specified family" do @addrinfo.pfamily.should == Socket::PF_INET6 end - it "returns the INET6 afamily" do + it "returns the specified family" do @addrinfo.afamily.should == Socket::AF_INET6 end @@ -113,11 +113,11 @@ @addrinfo.ip_port.should == 25 end - it "returns the INET6 pfamily" do + it "returns the specified family" do @addrinfo.pfamily.should == Socket::PF_INET6 end - it "returns the INET6 afamily" do + it "returns the specified family" do @addrinfo.afamily.should == Socket::AF_INET6 end @@ -147,11 +147,11 @@ @addrinfo.ip_port.should == 46102 end - it "returns the INET pfamily" do + it "returns the specified family" do @addrinfo.pfamily.should == Socket::PF_INET end - it "returns the INET afamily" do + it "returns the specified family" do @addrinfo.afamily.should == Socket::AF_INET end @@ -217,11 +217,11 @@ @addrinfo.ip_port.should == 46102 end - it "returns the INET pfamily" do + it "returns the specified family" do @addrinfo.pfamily.should == Socket::PF_INET end - it "returns the INET afamily" do + it "returns the specified family" do @addrinfo.afamily.should == Socket::AF_INET end @@ -247,11 +247,11 @@ @addrinfo.ip_port.should == 46102 end - it "returns the INET pfamily" do + it "returns the specified family" do @addrinfo.pfamily.should == Socket::PF_INET end - it "returns the INET afamily" do + it "returns the specified family" do @addrinfo.afamily.should == Socket::AF_INET end @@ -311,11 +311,11 @@ @addrinfo.ip_port.should == 46102 end - it "returns the INET pfamily" do + it "returns the specified family" do @addrinfo.pfamily.should == Socket::PF_INET end - it "returns the INET afamily" do + it "returns the specified family" do @addrinfo.afamily.should == Socket::AF_INET end @@ -514,13 +514,13 @@ @sockaddr = Socket.sockaddr_in(80, '127.0.0.1') end - it 'returns an Addrinfo with :PF_INET family' do + it 'returns an Addrinfo with the specified family' do addr = Addrinfo.new(@sockaddr, :PF_INET) addr.pfamily.should == Socket::PF_INET end - it 'returns an Addrinfo with :INET family' do + it 'returns an Addrinfo with the specified family' do addr = Addrinfo.new(@sockaddr, :INET) addr.pfamily.should == Socket::PF_INET @@ -544,13 +544,13 @@ @sockaddr = Socket.sockaddr_in(80, '127.0.0.1') end - it 'returns an Addrinfo with "PF_INET" family' do + it 'returns an Addrinfo with the specified family' do addr = Addrinfo.new(@sockaddr, 'PF_INET') addr.pfamily.should == Socket::PF_INET end - it 'returns an Addrinfo with "INET" family' do + it 'returns an Addrinfo with the specified family' do addr = Addrinfo.new(@sockaddr, 'INET') addr.pfamily.should == Socket::PF_INET diff --git a/spec/ruby/library/socket/basicsocket/recv_nonblock_spec.rb b/spec/ruby/library/socket/basicsocket/recv_nonblock_spec.rb index f2a6682f12b8ea..f2383513f286b2 100644 --- a/spec/ruby/library/socket/basicsocket/recv_nonblock_spec.rb +++ b/spec/ruby/library/socket/basicsocket/recv_nonblock_spec.rb @@ -112,60 +112,30 @@ @server.close unless @server.closed? end - ruby_version_is ""..."3.3" do - it "returns an empty String on a closed stream socket" do - ready = false - - t = Thread.new do - client = @server.accept - - Thread.pass while !ready - begin - client.recv_nonblock(10) - rescue IO::EAGAINWaitReadable - retry - end - ensure - client.close if client - end - - Thread.pass while t.status and t.status != "sleep" - t.status.should_not be_nil + it "returns nil on a closed stream socket" do + ready = false - socket = TCPSocket.new('127.0.0.1', @port) - socket.close - ready = true + t = Thread.new do + client = @server.accept - t.value.should == "" - end - end - - ruby_version_is "3.3" do - it "returns nil on a closed stream socket" do - ready = false - - t = Thread.new do - client = @server.accept - - Thread.pass while !ready - begin - client.recv_nonblock(10) - rescue IO::EAGAINWaitReadable - retry - end - ensure - client.close if client + Thread.pass while !ready + begin + client.recv_nonblock(10) + rescue IO::EAGAINWaitReadable + retry end + ensure + client.close if client + end - Thread.pass while t.status and t.status != "sleep" - t.status.should_not be_nil + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil - socket = TCPSocket.new('127.0.0.1', @port) - socket.close - ready = true + socket = TCPSocket.new('127.0.0.1', @port) + socket.close + ready = true - t.value.should be_nil - end + t.value.should be_nil end end end diff --git a/spec/ruby/library/socket/basicsocket/recv_spec.rb b/spec/ruby/library/socket/basicsocket/recv_spec.rb index a51920f52a092a..7581f1bc1533fa 100644 --- a/spec/ruby/library/socket/basicsocket/recv_spec.rb +++ b/spec/ruby/library/socket/basicsocket/recv_spec.rb @@ -184,42 +184,21 @@ @server.close unless @server.closed? end - ruby_version_is ""..."3.3" do - it "returns an empty String on a closed stream socket" do - t = Thread.new do - client = @server.accept - client.recv(10) - ensure - client.close if client - end - - Thread.pass while t.status and t.status != "sleep" - t.status.should_not be_nil - - socket = TCPSocket.new('127.0.0.1', @port) - socket.close - - t.value.should == "" + it "returns nil on a closed stream socket" do + t = Thread.new do + client = @server.accept + client.recv(10) + ensure + client.close if client end - end - - ruby_version_is "3.3" do - it "returns nil on a closed stream socket" do - t = Thread.new do - client = @server.accept - client.recv(10) - ensure - client.close if client - end - Thread.pass while t.status and t.status != "sleep" - t.status.should_not be_nil + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil - socket = TCPSocket.new('127.0.0.1', @port) - socket.close + socket = TCPSocket.new('127.0.0.1', @port) + socket.close - t.value.should be_nil - end + t.value.should be_nil end end diff --git a/spec/ruby/library/socket/basicsocket/recvmsg_nonblock_spec.rb b/spec/ruby/library/socket/basicsocket/recvmsg_nonblock_spec.rb index b5fdd7c93bee8d..d1cde4411bd8bc 100644 --- a/spec/ruby/library/socket/basicsocket/recvmsg_nonblock_spec.rb +++ b/spec/ruby/library/socket/basicsocket/recvmsg_nonblock_spec.rb @@ -235,64 +235,31 @@ @server.close unless @server.closed? end - ruby_version_is ""..."3.3" do - platform_is_not :windows do # #recvmsg_nonblock() raises 'Errno::EINVAL: Invalid argument - recvmsg(2)' - it "returns an empty String as received data on a closed stream socket" do - ready = false + platform_is_not :windows do + it "returns nil on a closed stream socket" do + ready = false - t = Thread.new do - client = @server.accept + t = Thread.new do + client = @server.accept - Thread.pass while !ready - begin - client.recvmsg_nonblock(10) - rescue IO::EAGAINWaitReadable - retry - end - ensure - client.close if client + Thread.pass while !ready + begin + client.recvmsg_nonblock(10) + rescue IO::EAGAINWaitReadable + retry end - - Thread.pass while t.status and t.status != "sleep" - t.status.should_not be_nil - - socket = TCPSocket.new('127.0.0.1', @port) - socket.close - ready = true - - t.value.should.is_a? Array - t.value[0].should == "" + ensure + client.close if client end - end - end - ruby_version_is "3.3" do - platform_is_not :windows do - it "returns nil on a closed stream socket" do - ready = false + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil - t = Thread.new do - client = @server.accept + socket = TCPSocket.new('127.0.0.1', @port) + socket.close + ready = true - Thread.pass while !ready - begin - client.recvmsg_nonblock(10) - rescue IO::EAGAINWaitReadable - retry - end - ensure - client.close if client - end - - Thread.pass while t.status and t.status != "sleep" - t.status.should_not be_nil - - socket = TCPSocket.new('127.0.0.1', @port) - socket.close - ready = true - - t.value.should be_nil - end + t.value.should be_nil end end end diff --git a/spec/ruby/library/socket/basicsocket/recvmsg_spec.rb b/spec/ruby/library/socket/basicsocket/recvmsg_spec.rb index 04ba1d74c768c1..cfa0f4c61d476f 100644 --- a/spec/ruby/library/socket/basicsocket/recvmsg_spec.rb +++ b/spec/ruby/library/socket/basicsocket/recvmsg_spec.rb @@ -208,46 +208,22 @@ @server.close unless @server.closed? end - ruby_version_is ""..."3.3" do - platform_is_not :windows do - it "returns an empty String as received data on a closed stream socket" do - t = Thread.new do - client = @server.accept - client.recvmsg(10) - ensure - client.close if client - end - - Thread.pass while t.status and t.status != "sleep" - t.status.should_not be_nil - - socket = TCPSocket.new('127.0.0.1', @port) - socket.close - - t.value.should.is_a? Array - t.value[0].should == "" + platform_is_not :windows do + it "returns nil on a closed stream socket" do + t = Thread.new do + client = @server.accept + client.recvmsg(10) + ensure + client.close if client end - end - end - - ruby_version_is "3.3" do - platform_is_not :windows do - it "returns nil on a closed stream socket" do - t = Thread.new do - client = @server.accept - client.recvmsg(10) - ensure - client.close if client - end - Thread.pass while t.status and t.status != "sleep" - t.status.should_not be_nil + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil - socket = TCPSocket.new('127.0.0.1', @port) - socket.close + socket = TCPSocket.new('127.0.0.1', @port) + socket.close - t.value.should be_nil - end + t.value.should be_nil end end end diff --git a/spec/ruby/library/socket/ipsocket/recvfrom_spec.rb b/spec/ruby/library/socket/ipsocket/recvfrom_spec.rb index b58903df237b9d..5e6a145c9bdaeb 100644 --- a/spec/ruby/library/socket/ipsocket/recvfrom_spec.rb +++ b/spec/ruby/library/socket/ipsocket/recvfrom_spec.rb @@ -83,43 +83,21 @@ @client.close unless @client.closed? end - ruby_version_is ""..."3.3" do - it "returns an empty String as received data on a closed stream socket" do - t = Thread.new do - client = @server.accept - message = client.recvfrom(10) - message - ensure - client.close if client - end - - Thread.pass while t.status and t.status != "sleep" - t.status.should_not be_nil - - @client.close - - t.value.should.is_a? Array - t.value[0].should == "" + it "returns nil on a closed stream socket" do + t = Thread.new do + client = @server.accept + message = client.recvfrom(10) + message + ensure + client.close if client end - end - - ruby_version_is "3.3" do - it "returns nil on a closed stream socket" do - t = Thread.new do - client = @server.accept - message = client.recvfrom(10) - message - ensure - client.close if client - end - Thread.pass while t.status and t.status != "sleep" - t.status.should_not be_nil + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil - @client.close + @client.close - t.value.should be_nil - end + t.value.should be_nil end end diff --git a/spec/ruby/library/socket/socket/getaddrinfo_spec.rb b/spec/ruby/library/socket/socket/getaddrinfo_spec.rb index 6576af52eeadc7..17ffeaccaf498b 100644 --- a/spec/ruby/library/socket/socket/getaddrinfo_spec.rb +++ b/spec/ruby/library/socket/socket/getaddrinfo_spec.rb @@ -107,22 +107,12 @@ res.each { |a| expected.should include(a) } end - ruby_version_is ""..."3.3" do - it "raises SocketError when fails to resolve address" do - -> { - Socket.getaddrinfo("www.kame.net", 80, "AF_UNIX") - }.should raise_error(SocketError) - end - end - - ruby_version_is "3.3" do - it "raises ResolutionError when fails to resolve address" do - -> { - Socket.getaddrinfo("www.kame.net", 80, "AF_UNIX") - }.should raise_error(Socket::ResolutionError) { |e| - [Socket::EAI_FAMILY, Socket::EAI_FAIL].should.include?(e.error_code) - } - end + it "raises ResolutionError when fails to resolve address" do + -> { + Socket.getaddrinfo("www.kame.net", 80, "AF_UNIX") + }.should raise_error(Socket::ResolutionError) { |e| + [Socket::EAI_FAMILY, Socket::EAI_FAIL].should.include?(e.error_code) + } end end end diff --git a/spec/ruby/library/socket/socket/getnameinfo_spec.rb b/spec/ruby/library/socket/socket/getnameinfo_spec.rb index af4a10c9c2baa5..48cc94bcd182ab 100644 --- a/spec/ruby/library/socket/socket/getnameinfo_spec.rb +++ b/spec/ruby/library/socket/socket/getnameinfo_spec.rb @@ -61,22 +61,12 @@ def should_be_valid_dns_name(name) name_info[1].should == 'discard' end - ruby_version_is ""..."3.3" do - it "raises SocketError when fails to resolve address" do - -> { - Socket.getnameinfo(["AF_UNIX", 80, "0.0.0.0"]) - }.should raise_error(SocketError) - end - end - - ruby_version_is "3.3" do - it "raises ResolutionError when fails to resolve address" do - -> { - Socket.getnameinfo(["AF_UNIX", 80, "0.0.0.0"]) - }.should raise_error(Socket::ResolutionError) { |e| - [Socket::EAI_FAMILY, Socket::EAI_FAIL].should.include?(e.error_code) - } - end + it "raises ResolutionError when fails to resolve address" do + -> { + Socket.getnameinfo(["AF_UNIX", 80, "0.0.0.0"]) + }.should raise_error(Socket::ResolutionError) { |e| + [Socket::EAI_FAMILY, Socket::EAI_FAIL].should.include?(e.error_code) + } end end diff --git a/spec/ruby/library/socket/socket/recvfrom_nonblock_spec.rb b/spec/ruby/library/socket/socket/recvfrom_nonblock_spec.rb index 01b42bcc52b4fa..38a9f5ff5bc3fe 100644 --- a/spec/ruby/library/socket/socket/recvfrom_nonblock_spec.rb +++ b/spec/ruby/library/socket/socket/recvfrom_nonblock_spec.rb @@ -158,61 +158,30 @@ @client.close unless @client.closed? end - ruby_version_is ""..."3.3" do - it "returns an empty String as received data on a closed stream socket" do - ready = false - - t = Thread.new do - client, _ = @server.accept - - Thread.pass while !ready - begin - client.recvfrom_nonblock(10) - rescue IO::EAGAINWaitReadable - retry - end - ensure - client.close if client - end - - Thread.pass while t.status and t.status != "sleep" - t.status.should_not be_nil + it "returns nil on a closed stream socket" do + ready = false - @client.connect(@server_addr) - @client.close - ready = true - - t.value.should.is_a? Array - t.value[0].should == "" - end - end + t = Thread.new do + client, _ = @server.accept - ruby_version_is "3.3" do - it "returns nil on a closed stream socket" do - ready = false - - t = Thread.new do - client, _ = @server.accept - - Thread.pass while !ready - begin - client.recvfrom_nonblock(10) - rescue IO::EAGAINWaitReadable - retry - end - ensure - client.close if client + Thread.pass while !ready + begin + client.recvfrom_nonblock(10) + rescue IO::EAGAINWaitReadable + retry end + ensure + client.close if client + end - Thread.pass while t.status and t.status != "sleep" - t.status.should_not be_nil + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil - @client.connect(@server_addr) - @client.close - ready = true + @client.connect(@server_addr) + @client.close + ready = true - t.value.should be_nil - end + t.value.should be_nil end end end diff --git a/spec/ruby/library/socket/socket/recvfrom_spec.rb b/spec/ruby/library/socket/socket/recvfrom_spec.rb index 6ba39ffcaf534c..cbbc162f6b0d28 100644 --- a/spec/ruby/library/socket/socket/recvfrom_spec.rb +++ b/spec/ruby/library/socket/socket/recvfrom_spec.rb @@ -111,43 +111,21 @@ @client.close unless @client.closed? end - ruby_version_is ""..."3.3" do - it "returns an empty String as received data on a closed stream socket" do - t = Thread.new do - client, _ = @server.accept - client.recvfrom(10) - ensure - client.close if client - end - - Thread.pass while t.status and t.status != "sleep" - t.status.should_not be_nil - - @client.connect(@server_addr) - @client.close - - t.value.should.is_a? Array - t.value[0].should == "" + it "returns nil on a closed stream socket" do + t = Thread.new do + client, _ = @server.accept + client.recvfrom(10) + ensure + client.close if client end - end - - ruby_version_is "3.3" do - it "returns nil on a closed stream socket" do - t = Thread.new do - client, _ = @server.accept - client.recvfrom(10) - ensure - client.close if client - end - Thread.pass while t.status and t.status != "sleep" - t.status.should_not be_nil + Thread.pass while t.status and t.status != "sleep" + t.status.should_not be_nil - @client.connect(@server_addr) - @client.close + @client.connect(@server_addr) + @client.close - t.value.should be_nil - end + t.value.should be_nil end end diff --git a/spec/ruby/library/stringscanner/named_captures_spec.rb b/spec/ruby/library/stringscanner/named_captures_spec.rb index a68d66c216a82e..927784a6c4a8a9 100644 --- a/spec/ruby/library/stringscanner/named_captures_spec.rb +++ b/spec/ruby/library/stringscanner/named_captures_spec.rb @@ -16,11 +16,9 @@ @s.named_captures.should == {} end - # https://github.com/ruby/strscan/issues/132 - ruby_bug "", ""..."3.3" do # fixed in strscan v3.0.7 - it "returns {} if there is no any matching done" do - @s.named_captures.should == {} - end + # https://github.com/ruby/strscan/issues/132 fixed in strscan v3.0.7 + it "returns {} if there is no any matching done" do + @s.named_captures.should == {} end it "returns nil for an optional named capturing group if it doesn't match" do diff --git a/spec/ruby/optional/capi/encoding_spec.rb b/spec/ruby/optional/capi/encoding_spec.rb index c14983c7ead703..734b5f125381db 100644 --- a/spec/ruby/optional/capi/encoding_spec.rb +++ b/spec/ruby/optional/capi/encoding_spec.rb @@ -745,4 +745,34 @@ ruby_exe(code, args: "2>&1", exit_status: 1).should.include?('too many encoding (> 256) (EncodingError)') end end + + describe "ONIGENC_IS_UNICODE" do + it "is true only for select UTF-related encodings" do + unicode = [ + Encoding::UTF_8, + Encoding::UTF8_DOCOMO, + Encoding::UTF8_KDDI, + Encoding::UTF8_MAC, + Encoding::UTF8_SOFTBANK, + Encoding::CESU_8, + Encoding::UTF_16LE, + Encoding::UTF_16BE, + Encoding::UTF_32LE, + Encoding::UTF_32BE + ] + unicode.each do |enc| + @s.should.ONIGENC_IS_UNICODE(enc) + end + + (Encoding.list - unicode).each { |enc| + @s.should_not.ONIGENC_IS_UNICODE(enc) + } + end + + # Redundant with the above but more explicit + it "is false for the dummy UTF-16 and UTF-32 encodings" do + @s.should_not.ONIGENC_IS_UNICODE(Encoding::UTF_16) + @s.should_not.ONIGENC_IS_UNICODE(Encoding::UTF_32) + end + end end diff --git a/spec/ruby/optional/capi/ext/encoding_spec.c b/spec/ruby/optional/capi/ext/encoding_spec.c index aa8662cfbd6426..98d4e2e3b772c8 100644 --- a/spec/ruby/optional/capi/ext/encoding_spec.c +++ b/spec/ruby/optional/capi/ext/encoding_spec.c @@ -324,6 +324,10 @@ static VALUE encoding_spec_rb_define_dummy_encoding(VALUE self, VALUE name) { return INT2NUM(rb_define_dummy_encoding(RSTRING_PTR(name))); } +static VALUE encoding_spec_ONIGENC_IS_UNICODE(VALUE self, VALUE encoding) { + return ONIGENC_IS_UNICODE(rb_to_encoding(encoding)) ? Qtrue : Qfalse; +} + void Init_encoding_spec(void) { VALUE cls; native_rb_encoding_pointer = (rb_encoding**) malloc(sizeof(rb_encoding*)); @@ -384,6 +388,7 @@ void Init_encoding_spec(void) { rb_define_method(cls, "ONIGENC_MBC_CASE_FOLD", encoding_spec_ONIGENC_MBC_CASE_FOLD, 1); rb_define_method(cls, "rb_enc_left_char_head", encoding_spec_rb_enc_left_char_head, 2); rb_define_method(cls, "rb_define_dummy_encoding", encoding_spec_rb_define_dummy_encoding, 1); + rb_define_method(cls, "ONIGENC_IS_UNICODE", encoding_spec_ONIGENC_IS_UNICODE, 1); } #ifdef __cplusplus diff --git a/spec/ruby/optional/capi/ext/kernel_spec.c b/spec/ruby/optional/capi/ext/kernel_spec.c index a8fed21b5900b6..eee324052d0936 100644 --- a/spec/ruby/optional/capi/ext/kernel_spec.c +++ b/spec/ruby/optional/capi/ext/kernel_spec.c @@ -1,4 +1,5 @@ #include "ruby.h" +#include "ruby/vm.h" #include "rubyspec.h" #include @@ -337,6 +338,15 @@ static VALUE kernel_spec_rb_set_end_proc(VALUE self, VALUE io) { return Qnil; } +static void at_exit_hook(ruby_vm_t *vm) { + puts("ruby_vm_at_exit hook ran"); +} + +static VALUE kernel_spec_ruby_vm_at_exit(VALUE self) { + ruby_vm_at_exit(at_exit_hook); + return self; +} + static VALUE kernel_spec_rb_f_sprintf(VALUE self, VALUE ary) { return rb_f_sprintf((int)RARRAY_LEN(ary), RARRAY_PTR(ary)); } @@ -434,6 +444,7 @@ void Init_kernel_spec(void) { rb_define_method(cls, "rb_yield_splat", kernel_spec_rb_yield_splat, 1); rb_define_method(cls, "rb_exec_recursive", kernel_spec_rb_exec_recursive, 1); rb_define_method(cls, "rb_set_end_proc", kernel_spec_rb_set_end_proc, 1); + rb_define_method(cls, "ruby_vm_at_exit", kernel_spec_ruby_vm_at_exit, 0); rb_define_method(cls, "rb_f_sprintf", kernel_spec_rb_f_sprintf, 1); rb_define_method(cls, "rb_str_format", kernel_spec_rb_str_format, 3); rb_define_method(cls, "rb_make_backtrace", kernel_spec_rb_make_backtrace, 0); diff --git a/spec/ruby/optional/capi/ext/string_spec.c b/spec/ruby/optional/capi/ext/string_spec.c index 094013e049cbf6..74aa9e56e816fe 100644 --- a/spec/ruby/optional/capi/ext/string_spec.c +++ b/spec/ruby/optional/capi/ext/string_spec.c @@ -581,6 +581,14 @@ static VALUE string_spec_rb_str_to_interned_str(VALUE self, VALUE str) { return rb_str_to_interned_str(str); } +static VALUE string_spec_rb_interned_str(VALUE self, VALUE str, VALUE len) { + return rb_interned_str(RSTRING_PTR(str), FIX2LONG(len)); +} + +static VALUE string_spec_rb_interned_str_cstr(VALUE self, VALUE str) { + return rb_interned_str_cstr(RSTRING_PTR(str)); +} + void Init_string_spec(void) { VALUE cls = rb_define_class("CApiStringSpecs", rb_cObject); rb_define_method(cls, "rb_cstr2inum", string_spec_rb_cstr2inum, 2); @@ -681,6 +689,8 @@ void Init_string_spec(void) { rb_define_method(cls, "rb_enc_interned_str_cstr", string_spec_rb_enc_interned_str_cstr, 2); rb_define_method(cls, "rb_enc_interned_str", string_spec_rb_enc_interned_str, 3); rb_define_method(cls, "rb_str_to_interned_str", string_spec_rb_str_to_interned_str, 1); + rb_define_method(cls, "rb_interned_str", string_spec_rb_interned_str, 2); + rb_define_method(cls, "rb_interned_str_cstr", string_spec_rb_interned_str_cstr, 1); } #ifdef __cplusplus diff --git a/spec/ruby/optional/capi/io_spec.rb b/spec/ruby/optional/capi/io_spec.rb index ab7a7fc8f6f661..dc4ac3e3744ce8 100644 --- a/spec/ruby/optional/capi/io_spec.rb +++ b/spec/ruby/optional/capi/io_spec.rb @@ -494,166 +494,164 @@ end end - ruby_version_is "3.3" do - describe "rb_io_mode" do - it "returns the mode" do - (@o.rb_io_mode(@r_io) & 0b11).should == 0b01 - (@o.rb_io_mode(@w_io) & 0b11).should == 0b10 - (@o.rb_io_mode(@rw_io) & 0b11).should == 0b11 - end + describe "rb_io_mode" do + it "returns the mode" do + (@o.rb_io_mode(@r_io) & 0b11).should == 0b01 + (@o.rb_io_mode(@w_io) & 0b11).should == 0b10 + (@o.rb_io_mode(@rw_io) & 0b11).should == 0b11 end + end - describe "rb_io_path" do - it "returns the IO#path" do - @o.rb_io_path(@r_io).should == @r_io.path - @o.rb_io_path(@rw_io).should == @rw_io.path - @o.rb_io_path(@rw_io).should == @name - end + describe "rb_io_path" do + it "returns the IO#path" do + @o.rb_io_path(@r_io).should == @r_io.path + @o.rb_io_path(@rw_io).should == @rw_io.path + @o.rb_io_path(@rw_io).should == @name end + end - describe "rb_io_closed_p" do - it "returns false when io is not closed" do - @o.rb_io_closed_p(@r_io).should == false - @r_io.closed?.should == false - end + describe "rb_io_closed_p" do + it "returns false when io is not closed" do + @o.rb_io_closed_p(@r_io).should == false + @r_io.closed?.should == false + end - it "returns true when io is closed" do - @r_io.close + it "returns true when io is closed" do + @r_io.close - @o.rb_io_closed_p(@r_io).should == true - @r_io.closed?.should == true - end + @o.rb_io_closed_p(@r_io).should == true + @r_io.closed?.should == true end + end - quarantine! do # "Errno::EBADF: Bad file descriptor" at closing @r_io, @rw_io etc in the after :each hook - describe "rb_io_open_descriptor" do - it "creates a new IO instance" do - io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {}) - io.should.is_a?(IO) - end - - it "return an instance of the specified class" do - io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {}) - io.class.should == File + quarantine! do # "Errno::EBADF: Bad file descriptor" at closing @r_io, @rw_io etc in the after :each hook + describe "rb_io_open_descriptor" do + it "creates a new IO instance" do + io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {}) + io.should.is_a?(IO) + end - io = @o.rb_io_open_descriptor(IO, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {}) - io.class.should == IO - end + it "return an instance of the specified class" do + io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {}) + io.class.should == File - it "sets the specified file descriptor" do - io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {}) - io.fileno.should == @r_io.fileno - end + io = @o.rb_io_open_descriptor(IO, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {}) + io.class.should == IO + end - it "sets the specified path" do - io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {}) - io.path.should == "a.txt" - end + it "sets the specified file descriptor" do + io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {}) + io.fileno.should == @r_io.fileno + end - it "sets the specified mode" do - io = @o.rb_io_open_descriptor(File, @r_io.fileno, CApiIOSpecs::FMODE_BINMODE, "a.txt", 60, "US-ASCII", "UTF-8", 0, {}) - io.should.binmode? + it "sets the specified path" do + io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {}) + io.path.should == "a.txt" + end - io = @o.rb_io_open_descriptor(File, @r_io.fileno, CApiIOSpecs::FMODE_TEXTMODE, "a.txt", 60, "US-ASCII", "UTF-8", 0, {}) - io.should_not.binmode? - end + it "sets the specified mode" do + io = @o.rb_io_open_descriptor(File, @r_io.fileno, CApiIOSpecs::FMODE_BINMODE, "a.txt", 60, "US-ASCII", "UTF-8", 0, {}) + io.should.binmode? - it "sets the specified timeout" do - io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {}) - io.timeout.should == 60 - end + io = @o.rb_io_open_descriptor(File, @r_io.fileno, CApiIOSpecs::FMODE_TEXTMODE, "a.txt", 60, "US-ASCII", "UTF-8", 0, {}) + io.should_not.binmode? + end - it "sets the specified internal encoding" do - io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {}) - io.internal_encoding.should == Encoding::US_ASCII - end + it "sets the specified timeout" do + io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {}) + io.timeout.should == 60 + end - it "sets the specified external encoding" do - io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {}) - io.external_encoding.should == Encoding::UTF_8 - end + it "sets the specified internal encoding" do + io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {}) + io.internal_encoding.should == Encoding::US_ASCII + end - it "does not apply the specified encoding flags" do - name = tmp("rb_io_open_descriptor_specs") - File.write(name, "123\r\n456\n89") - file = File.open(name, "r") + it "sets the specified external encoding" do + io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {}) + io.external_encoding.should == Encoding::UTF_8 + end - io = @o.rb_io_open_descriptor(File, file.fileno, CApiIOSpecs::FMODE_READABLE, "a.txt", 60, "US-ASCII", "UTF-8", CApiIOSpecs::ECONV_UNIVERSAL_NEWLINE_DECORATOR, {}) - io.read_nonblock(20).should == "123\r\n456\n89" - ensure - file.close - rm_r name - end + it "does not apply the specified encoding flags" do + name = tmp("rb_io_open_descriptor_specs") + File.write(name, "123\r\n456\n89") + file = File.open(name, "r") - it "ignores the IO open options" do - io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {external_encoding: "windows-1251"}) - io.external_encoding.should == Encoding::UTF_8 + io = @o.rb_io_open_descriptor(File, file.fileno, CApiIOSpecs::FMODE_READABLE, "a.txt", 60, "US-ASCII", "UTF-8", CApiIOSpecs::ECONV_UNIVERSAL_NEWLINE_DECORATOR, {}) + io.read_nonblock(20).should == "123\r\n456\n89" + ensure + file.close + rm_r name + end - io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {internal_encoding: "windows-1251"}) - io.internal_encoding.should == Encoding::US_ASCII + it "ignores the IO open options" do + io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {external_encoding: "windows-1251"}) + io.external_encoding.should == Encoding::UTF_8 - io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {encoding: "windows-1251:binary"}) - io.external_encoding.should == Encoding::UTF_8 - io.internal_encoding.should == Encoding::US_ASCII + io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {internal_encoding: "windows-1251"}) + io.internal_encoding.should == Encoding::US_ASCII - io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {textmode: false}) - io.should_not.binmode? + io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {encoding: "windows-1251:binary"}) + io.external_encoding.should == Encoding::UTF_8 + io.internal_encoding.should == Encoding::US_ASCII - io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {binmode: true}) - io.should_not.binmode? + io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {textmode: false}) + io.should_not.binmode? - io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {autoclose: false}) - io.should.autoclose? + io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {binmode: true}) + io.should_not.binmode? - io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {path: "a.txt"}) - io.path.should == "a.txt" - end + io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {autoclose: false}) + io.should.autoclose? - it "ignores the IO encoding options" do - io = @o.rb_io_open_descriptor(File, @w_io.fileno, CApiIOSpecs::FMODE_WRITABLE, "a.txt", 60, "US-ASCII", "UTF-8", 0, {crlf_newline: true}) + io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {path: "a.txt"}) + io.path.should == "a.txt" + end - io.write("123\r\n456\n89") - io.flush + it "ignores the IO encoding options" do + io = @o.rb_io_open_descriptor(File, @w_io.fileno, CApiIOSpecs::FMODE_WRITABLE, "a.txt", 60, "US-ASCII", "UTF-8", 0, {crlf_newline: true}) - @r_io.read_nonblock(20).should == "123\r\n456\n89" - end + io.write("123\r\n456\n89") + io.flush - it "allows wrong mode" do - io = @o.rb_io_open_descriptor(File, @w_io.fileno, CApiIOSpecs::FMODE_READABLE, "a.txt", 60, "US-ASCII", "UTF-8", 0, {}) - io.should.is_a?(File) + @r_io.read_nonblock(20).should == "123\r\n456\n89" + end - platform_is_not :windows do - -> { io.read_nonblock(1) }.should raise_error(Errno::EBADF) - end + it "allows wrong mode" do + io = @o.rb_io_open_descriptor(File, @w_io.fileno, CApiIOSpecs::FMODE_READABLE, "a.txt", 60, "US-ASCII", "UTF-8", 0, {}) + io.should.is_a?(File) - platform_is :windows do - -> { io.read_nonblock(1) }.should raise_error(IO::EWOULDBLOCKWaitReadable) - end + platform_is_not :windows do + -> { io.read_nonblock(1) }.should raise_error(Errno::EBADF) end - it "tolerates NULL as rb_io_encoding *encoding parameter" do - io = @o.rb_io_open_descriptor_without_encoding(File, @r_io.fileno, 0, "a.txt", 60) - io.should.is_a?(File) + platform_is :windows do + -> { io.read_nonblock(1) }.should raise_error(IO::EWOULDBLOCKWaitReadable) end + end - it "deduplicates path String" do - path = "a.txt".dup - io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, path, 60, "US-ASCII", "UTF-8", 0, {}) - io.path.should_not equal(path) + it "tolerates NULL as rb_io_encoding *encoding parameter" do + io = @o.rb_io_open_descriptor_without_encoding(File, @r_io.fileno, 0, "a.txt", 60) + io.should.is_a?(File) + end - path = "a.txt".freeze - io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, path, 60, "US-ASCII", "UTF-8", 0, {}) - io.path.should_not equal(path) - end + it "deduplicates path String" do + path = "a.txt".dup + io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, path, 60, "US-ASCII", "UTF-8", 0, {}) + io.path.should_not equal(path) + + path = "a.txt".freeze + io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, path, 60, "US-ASCII", "UTF-8", 0, {}) + io.path.should_not equal(path) + end - it "calls #to_str to convert a path to a String" do - path = Object.new - def path.to_str; "a.txt"; end + it "calls #to_str to convert a path to a String" do + path = Object.new + def path.to_str; "a.txt"; end - io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, path, 60, "US-ASCII", "UTF-8", 0, {}) + io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, path, 60, "US-ASCII", "UTF-8", 0, {}) - io.path.should == "a.txt" - end + io.path.should == "a.txt" end end end diff --git a/spec/ruby/optional/capi/kernel_spec.rb b/spec/ruby/optional/capi/kernel_spec.rb index 6633ee50c1f8ac..0a2362fb304ab8 100644 --- a/spec/ruby/optional/capi/kernel_spec.rb +++ b/spec/ruby/optional/capi/kernel_spec.rb @@ -703,6 +703,12 @@ def proc_caller end end + describe "ruby_vm_at_exit" do + it "runs a C function after the VM is terminated" do + ruby_exe("require #{kernel_path.inspect}; CApiKernelSpecs.new.ruby_vm_at_exit").should == "ruby_vm_at_exit hook ran\n" + end + end + describe "rb_f_sprintf" do it "returns a string according to format and arguments" do @s.rb_f_sprintf(["%d %f %s", 10, 2.5, "test"]).should == "10 2.500000 test" diff --git a/spec/ruby/optional/capi/object_spec.rb b/spec/ruby/optional/capi/object_spec.rb index 8b4d8a9bba0e58..6716fd9e33766c 100644 --- a/spec/ruby/optional/capi/object_spec.rb +++ b/spec/ruby/optional/capi/object_spec.rb @@ -1004,7 +1004,6 @@ def reach it "calls the callback function for each cvar and ivar on a class" do exp = [:@@cvar, :foo, :@@cvar2, :bar, :@ivar, :baz] - exp.unshift(:__classpath__, 'CApiObjectSpecs::CVars') if RUBY_VERSION < "3.3" ary = @o.rb_ivar_foreach(CApiObjectSpecs::CVars) ary.should == exp @@ -1012,7 +1011,6 @@ def reach it "calls the callback function for each cvar and ivar on a module" do exp = [:@@mvar, :foo, :@@mvar2, :bar, :@ivar, :baz] - exp.unshift(:__classpath__, 'CApiObjectSpecs::MVars') if RUBY_VERSION < "3.3" ary = @o.rb_ivar_foreach(CApiObjectSpecs::MVars) ary.should == exp diff --git a/spec/ruby/optional/capi/spec_helper.rb b/spec/ruby/optional/capi/spec_helper.rb index e7abf46e6ccf65..d937c967d062fc 100644 --- a/spec/ruby/optional/capi/spec_helper.rb +++ b/spec/ruby/optional/capi/spec_helper.rb @@ -59,7 +59,11 @@ def compile_extension(name) tmpdir = tmp("cext_#{name}") Dir.mkdir(tmpdir) begin - ["#{core_ext_dir}/rubyspec.h", "#{spec_ext_dir}/#{ext}.c"].each do |file| + files = ["#{core_ext_dir}/rubyspec.h", "#{spec_ext_dir}/#{ext}.c"] + if spec_ext_dir != core_ext_dir + files += Dir.glob("#{spec_ext_dir}/*.h") + end + files.each do |file| if cxx and file.end_with?('.c') cp file, "#{tmpdir}/#{File.basename(file, '.c')}.cpp" else diff --git a/spec/ruby/optional/capi/string_spec.rb b/spec/ruby/optional/capi/string_spec.rb index 72f20ee6a52455..889f0a6cfe5d51 100644 --- a/spec/ruby/optional/capi/string_spec.rb +++ b/spec/ruby/optional/capi/string_spec.rb @@ -1369,8 +1369,133 @@ def inspect result1.should_not.equal?(result2) end + it "preserves the encoding of the original string" do + result1 = @s.rb_str_to_interned_str("hello".dup.force_encoding(Encoding::US_ASCII)) + result2 = @s.rb_str_to_interned_str("hello".dup.force_encoding(Encoding::UTF_8)) + result1.encoding.should == Encoding::US_ASCII + result2.encoding.should == Encoding::UTF_8 + end + it "returns the same string as String#-@" do @s.rb_str_to_interned_str("hello").should.equal?(-"hello") end end + + describe "rb_interned_str" do + it "returns a frozen string" do + str = "hello" + result = @s.rb_interned_str(str, str.bytesize) + result.should.is_a?(String) + result.should.frozen? + result.encoding.should == Encoding::US_ASCII + end + + it "returns the same frozen string" do + str = "hello" + result1 = @s.rb_interned_str(str, str.bytesize) + result2 = @s.rb_interned_str(str, str.bytesize) + result1.should.equal?(result2) + end + + it "supports strings with embedded null bytes" do + str = "foo\x00bar\x00baz".b + result = @s.rb_interned_str(str, str.bytesize) + result.should == str + end + + it "return US_ASCII encoding for an empty string" do + result = @s.rb_interned_str("", 0) + result.should == "" + result.encoding.should == Encoding::US_ASCII + end + + it "returns US_ASCII encoding for strings of only 7 bit ASCII" do + 0x00.upto(0x7f).each do |char| + result = @s.rb_interned_str(char.chr, 1) + result.encoding.should == Encoding::US_ASCII + end + end + + ruby_bug "21842", ""..."4.1" do + it "returns BINARY encoding for strings that use the 8th bit" do + 0x80.upto(0xff) do |char| + result = @s.rb_interned_str(char.chr, 1) + result.encoding.should == Encoding::BINARY + end + end + end + + it 'returns the same string when using non-ascii characters' do + str = 'こんにちは' + result1 = @s.rb_interned_str(str, str.bytesize) + result2 = @s.rb_interned_str(str, str.bytesize) + result1.should.equal?(result2) + end + + ruby_bug "21842", ""..."4.1" do + it "returns the same string as String#-@" do + str = "hello".dup.force_encoding(Encoding::US_ASCII) + @s.rb_interned_str(str, str.bytesize).should.equal?(-str) + end + end + end + + describe "rb_interned_str_cstr" do + it "returns a frozen string" do + str = "hello" + result = @s.rb_interned_str_cstr(str) + result.should.is_a?(String) + result.should.frozen? + result.encoding.should == Encoding::US_ASCII + end + + it "returns the same frozen string" do + str = "hello" + result1 = @s.rb_interned_str_cstr(str) + result2 = @s.rb_interned_str_cstr(str) + result1.should.equal?(result2) + end + + it "does not support strings with embedded null bytes" do + str = "foo\x00bar\x00baz".b + result = @s.rb_interned_str_cstr(str) + result.should == "foo" + end + + it "return US_ASCII encoding for an empty string" do + result = @s.rb_interned_str_cstr("") + result.should == "" + result.encoding.should == Encoding::US_ASCII + end + + it "returns US_ASCII encoding for strings of only 7 bit ASCII" do + 0x01.upto(0x7f).each do |char| + result = @s.rb_interned_str_cstr(char.chr) + result.encoding.should == Encoding::US_ASCII + end + end + + ruby_bug "21842", ""..."4.1" do + it "returns BINARY encoding for strings that use the 8th bit" do + 0x80.upto(0xff) do |char| + result = @s.rb_interned_str_cstr(char.chr) + result.encoding.should == Encoding::BINARY + end + end + end + + it 'returns the same string when using non-ascii characters' do + str = 'こんにちは' + result1 = @s.rb_interned_str_cstr(str) + result2 = @s.rb_interned_str_cstr(str) + result1.should.equal?(result2) + end + + ruby_bug "21842", ""..."4.1" do + it "returns the same string as String#-@" do + str = "hello".dup.force_encoding(Encoding::US_ASCII) + @s.rb_interned_str_cstr(str).should.equal?(-str) + end + end + end end diff --git a/spec/ruby/optional/capi/struct_spec.rb b/spec/ruby/optional/capi/struct_spec.rb index cc8d7f932e53b1..3f9eff52bc0b10 100644 --- a/spec/ruby/optional/capi/struct_spec.rb +++ b/spec/ruby/optional/capi/struct_spec.rb @@ -239,78 +239,76 @@ end end -ruby_version_is "3.3" do - describe "C-API Data function" do - before :all do - @s = CApiStructSpecs.new - @klass = @s.rb_data_define(nil, "a", "b", "c") - end - - describe "rb_data_define" do - it "returns a subclass of Data class when passed nil as the first argument" do - @klass.should.is_a? Class - @klass.superclass.should == Data - end - - it "returns a subclass of a class when passed as the first argument" do - superclass = Class.new(Data) - klass = @s.rb_data_define(superclass, "a", "b", "c") - - klass.should.is_a? Class - klass.superclass.should == superclass - end - - it "creates readers for the members" do - obj = @klass.new(1, 2, 3) - - obj.a.should == 1 - obj.b.should == 2 - obj.c.should == 3 - end - - it "returns the member names as Symbols" do - obj = @klass.new(0, 0, 0) - - obj.members.should == [:a, :b, :c] - end - - it "raises an ArgumentError if arguments contain duplicate member name" do - -> { @s.rb_data_define(nil, "a", "b", "a") }.should raise_error(ArgumentError) - end - - it "raises when first argument is not a class" do - -> { @s.rb_data_define([], "a", "b", "c") }.should raise_error(TypeError, "wrong argument type Array (expected Class)") - end - end - - describe "rb_struct_initialize" do - it "sets all members for a Data instance" do - data = @klass.allocate - @s.rb_struct_initialize(data, [1, 2, 3]).should == nil - data.a.should == 1 - data.b.should == 2 - data.c.should == 3 - end - - it "freezes the Data instance" do - data = @klass.allocate - @s.rb_struct_initialize(data, [1, 2, 3]).should == nil - data.should.frozen? - -> { @s.rb_struct_initialize(data, [1, 2, 3]) }.should raise_error(FrozenError) - end - - it "raises ArgumentError if too many values" do - data = @klass.allocate - -> { @s.rb_struct_initialize(data, [1, 2, 3, 4]) }.should raise_error(ArgumentError, "struct size differs") - end - - it "treats missing values as nil" do - data = @klass.allocate - @s.rb_struct_initialize(data, [1, 2]).should == nil - data.a.should == 1 - data.b.should == 2 - data.c.should == nil - end +describe "C-API Data function" do + before :all do + @s = CApiStructSpecs.new + @klass = @s.rb_data_define(nil, "a", "b", "c") + end + + describe "rb_data_define" do + it "returns a subclass of Data class when passed nil as the first argument" do + @klass.should.is_a? Class + @klass.superclass.should == Data + end + + it "returns a subclass of a class when passed as the first argument" do + superclass = Class.new(Data) + klass = @s.rb_data_define(superclass, "a", "b", "c") + + klass.should.is_a? Class + klass.superclass.should == superclass + end + + it "creates readers for the members" do + obj = @klass.new(1, 2, 3) + + obj.a.should == 1 + obj.b.should == 2 + obj.c.should == 3 + end + + it "returns the member names as Symbols" do + obj = @klass.new(0, 0, 0) + + obj.members.should == [:a, :b, :c] + end + + it "raises an ArgumentError if arguments contain duplicate member name" do + -> { @s.rb_data_define(nil, "a", "b", "a") }.should raise_error(ArgumentError) + end + + it "raises when first argument is not a class" do + -> { @s.rb_data_define([], "a", "b", "c") }.should raise_error(TypeError, "wrong argument type Array (expected Class)") + end + end + + describe "rb_struct_initialize" do + it "sets all members for a Data instance" do + data = @klass.allocate + @s.rb_struct_initialize(data, [1, 2, 3]).should == nil + data.a.should == 1 + data.b.should == 2 + data.c.should == 3 + end + + it "freezes the Data instance" do + data = @klass.allocate + @s.rb_struct_initialize(data, [1, 2, 3]).should == nil + data.should.frozen? + -> { @s.rb_struct_initialize(data, [1, 2, 3]) }.should raise_error(FrozenError) + end + + it "raises ArgumentError if too many values" do + data = @klass.allocate + -> { @s.rb_struct_initialize(data, [1, 2, 3, 4]) }.should raise_error(ArgumentError, "struct size differs") + end + + it "treats missing values as nil" do + data = @klass.allocate + @s.rb_struct_initialize(data, [1, 2]).should == nil + data.a.should == 1 + data.b.should == 2 + data.c.should == nil end end end diff --git a/spec/ruby/security/cve_2020_10663_spec.rb b/spec/ruby/security/cve_2020_10663_spec.rb index c44a13a0dd4b5d..7f42c407420b46 100644 --- a/spec/ruby/security/cve_2020_10663_spec.rb +++ b/spec/ruby/security/cve_2020_10663_spec.rb @@ -21,7 +21,7 @@ def to_json(*args) guard -> { JSON.const_defined?(:Pure) or - version_is(JSON::VERSION, '2.3.0') + version_is(JSON::VERSION, '2.3.0'...'2.11.0') } do describe "CVE-2020-10663 is resisted by" do it "only creating custom objects if passed create_additions: true or using JSON.load" do diff --git a/spec/ruby/shared/kernel/at_exit.rb b/spec/ruby/shared/kernel/at_exit.rb index 29db79bb391428..d57ab73920f3fa 100644 --- a/spec/ruby/shared/kernel/at_exit.rb +++ b/spec/ruby/shared/kernel/at_exit.rb @@ -60,10 +60,7 @@ result = ruby_exe('{', options: "-r#{script}", args: "2>&1", exit_status: 1) $?.should_not.success? result.should.include?("handler ran\n") - - # it's tempting not to rely on error message and rely only on exception class name, - # but CRuby before 3.2 doesn't print class name for syntax error - result.should include_any_of("syntax error", "SyntaxError") + result.should include("SyntaxError") end it "calls the nested handler right after the outer one if a handler is nested into another handler" do diff --git a/spec/ruby/shared/queue/freeze.rb b/spec/ruby/shared/queue/freeze.rb index 4c506a42355f62..5dedd005df4975 100644 --- a/spec/ruby/shared/queue/freeze.rb +++ b/spec/ruby/shared/queue/freeze.rb @@ -1,18 +1,8 @@ describe :queue_freeze, shared: true do - ruby_version_is ""..."3.3" do - it "can be frozen" do - queue = @object.call + it "raises an exception when freezing" do + queue = @object.call + -> { queue.freeze - queue.should.frozen? - end - end - - ruby_version_is "3.3" do - it "raises an exception when freezing" do - queue = @object.call - -> { - queue.freeze - }.should raise_error(TypeError, "cannot freeze #{queue}") - end + }.should raise_error(TypeError, "cannot freeze #{queue}") end end diff --git a/spec/ruby/shared/string/start_with.rb b/spec/ruby/shared/string/start_with.rb index 4b947a3bbf0ea8..9592eda4d43d31 100644 --- a/spec/ruby/shared/string/start_with.rb +++ b/spec/ruby/shared/string/start_with.rb @@ -70,15 +70,7 @@ $1.should be_nil end - ruby_version_is ""..."3.3" do - it "does not check that we are not matching part of a character" do - "\xC3\xA9".send(@method).should.start_with?("\xC3") - end - end - - ruby_version_is "3.3" do # #19784 - it "checks that we are not matching part of a character" do - "\xC3\xA9".send(@method).should_not.start_with?("\xC3") - end + it "checks that we are not matching part of a character" do + "\xC3\xA9".send(@method).should_not.start_with?("\xC3") end end From c8f01d599b962a7ae0183795162cbdfd9ee7aeb3 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Wed, 28 Jan 2026 22:31:09 +0100 Subject: [PATCH 05/11] ruby/spec no longer supports 3.2 --- .github/workflows/spec_guards.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/spec_guards.yml b/.github/workflows/spec_guards.yml index 856d6f61eb9303..0a5104f1419298 100644 --- a/.github/workflows/spec_guards.yml +++ b/.github/workflows/spec_guards.yml @@ -39,7 +39,6 @@ jobs: # Specs from ruby/spec should still run on all supported Ruby versions. # This also ensures the needed ruby_version_is guards are there, see spec/README.md. ruby: - - ruby-3.2 - ruby-3.3 - ruby-3.4 - ruby-4.0 From 554ca2eb622d1cfe886b419a54293d231a6c19f7 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 27 Jan 2026 21:05:57 -0500 Subject: [PATCH 06/11] [DOC] Fix broken link in Coverage.setup --- ext/coverage/coverage.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/coverage/coverage.c b/ext/coverage/coverage.c index 93acdb24806f78..41f33f4fb8c9f3 100644 --- a/ext/coverage/coverage.c +++ b/ext/coverage/coverage.c @@ -70,7 +70,7 @@ rb_coverage_supported(VALUE self, VALUE _mode) * If +lines+ is enabled, +oneshot_lines+ cannot be enabled. * See {Lines Coverage}[rdoc-ref:Coverage@Lines+Coverage]. * - +branches+: Enables branch coverage that records the number of times each - * branch in each conditional was executed. See {Branches Coverage}[rdoc-ref:Coverage@Branch+Coverage]. + * branch in each conditional was executed. See {Branches Coverage}[rdoc-ref:Coverage@Branches+Coverage]. * - +methods+: Enables method coverage that records the number of times each method was exectued. * See {Methods Coverage}[rdoc-ref:Coverage@Methods+Coverage]. * - +eval+: Enables coverage for evaluations (e.g. Kernel#eval, Module#class_eval). From f2fde274506fa1731e576d9fca237764103b56db Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 27 Jan 2026 18:28:44 +0900 Subject: [PATCH 07/11] [ruby/rubygems] Only use parent source with Git and Path sources https://github.com/ruby/rubygems/commit/c5da276610 --- lib/bundler/definition.rb | 2 +- spec/bundler/install/gemfile/sources_spec.rb | 41 ++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 5ab577f504c39d..639740e46b6b04 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -1077,7 +1077,7 @@ def converge_specs(specs) end end - if parent_dep + if parent_dep && parent_dep.source.is_a?(Source::Path) replacement_source = parent_dep.source else replacement_source = sources.get(lockfile_source) diff --git a/spec/bundler/install/gemfile/sources_spec.rb b/spec/bundler/install/gemfile/sources_spec.rb index 90f87ed0c5daea..69b0816a18999e 100644 --- a/spec/bundler/install/gemfile/sources_spec.rb +++ b/spec/bundler/install/gemfile/sources_spec.rb @@ -1195,4 +1195,45 @@ expect(gem_section).not_to include("activerecord (7.0.0)") end end + + context "when a scoped rubygems source is missing a transitive dependency" do + before do + build_repo2 do + build_gem "fallback_dep", "1.0.0" + build_gem "foo", "1.0.0" + end + + build_repo3 do + build_gem "private_parent", "1.0.0" do |s| + s.add_dependency "fallback_dep" + end + end + + gemfile <<-G + source "https://gem.repo2" + + gem "foo" + + source "https://gem.repo3" do + gem "private_parent", "1.0.0" + end + G + + bundle :install, artifice: "compact_index" + end + + it "falls back to the default rubygems source for that dependency" do + build_repo2 do + build_gem "foo", "2.0.0" + end + + system_gems [] + + bundle "update foo", artifice: "compact_index" + + expect(the_bundle).to include_gems("private_parent 1.0.0", "fallback_dep 1.0.0", "foo 2.0.0") + expect(the_bundle).to include_gems("private_parent 1.0.0", source: "remote3") + expect(the_bundle).to include_gems("fallback_dep 1.0.0", source: "remote2") + end + end end From 0c30897d0bf579ee7be08fc828932e1bac1196aa Mon Sep 17 00:00:00 2001 From: Andrii Furmanets Date: Sun, 23 Nov 2025 19:19:22 +0200 Subject: [PATCH 08/11] [ruby/rubygems] Remove outdated TODO in RemoteFetcher https://github.com/ruby/rubygems/commit/cc81b8b228 --- lib/rubygems/remote_fetcher.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rubygems/remote_fetcher.rb b/lib/rubygems/remote_fetcher.rb index 805f7aaf82ed1a..151c6fd4d8bbdf 100644 --- a/lib/rubygems/remote_fetcher.rb +++ b/lib/rubygems/remote_fetcher.rb @@ -174,7 +174,7 @@ def download(spec, source_uri, install_dir = Gem.dir) end verbose "Using local gem #{local_gem_path}" - when nil then # TODO: test for local overriding cache + when nil then source_path = if Gem.win_platform? && source_uri.scheme && !source_uri.path.include?(":") "#{source_uri.scheme}:#{source_uri.path}" From fa09afb15c9cf901d84e2963b86c4c7a7d0e104e Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Wed, 28 Jan 2026 21:23:15 -0500 Subject: [PATCH 09/11] [ruby/prism] Support `version: "nearest"`. This clamps to supported versions based on the current Ruby version. https://github.com/ruby/prism/commit/eb63748e8b --- lib/prism/ffi.rb | 24 ++++++++++++++++++++---- prism/extension.c | 27 ++++++++++++++++++++++----- prism/options.h | 8 +++++++- 3 files changed, 49 insertions(+), 10 deletions(-) diff --git a/lib/prism/ffi.rb b/lib/prism/ffi.rb index d4c9d60c9aa2a4..57d878a33fa299 100644 --- a/lib/prism/ffi.rb +++ b/lib/prism/ffi.rb @@ -423,10 +423,26 @@ def dump_options_command_line(options) # Return the value that should be dumped for the version option. def dump_options_version(version) - current = version == "current" + checking = + case version + when "current" + RUBY_VERSION + when "latest" + nil + when "nearest" + if RUBY_VERSION <= "3.3" + "3.3" + elsif RUBY_VERSION >= "4.1" + "4.1" + else + RUBY_VERSION + end + else + version + end - case current ? RUBY_VERSION : version - when nil, "latest" + case checking + when nil 0 # Handled in pm_parser_init when /\A3\.3(\.\d+)?\z/ 1 @@ -437,7 +453,7 @@ def dump_options_version(version) when /\A4\.1(\.\d+)?\z/ 4 else - if current + if version == "current" raise CurrentVersionError, RUBY_VERSION else raise ArgumentError, "invalid version: #{version}" diff --git a/prism/extension.c b/prism/extension.c index 400546a4ce0364..cde10bf360df2a 100644 --- a/prism/extension.c +++ b/prism/extension.c @@ -201,9 +201,24 @@ build_options_i(VALUE key, VALUE value, VALUE argument) { const char *version = check_string(value); if (RSTRING_LEN(value) == 7 && strncmp(version, "current", 7) == 0) { - const char *current_version = RSTRING_PTR(rb_const_get(rb_cObject, rb_intern("RUBY_VERSION"))); - if (!pm_options_version_set(options, current_version, 3)) { - rb_exc_raise(rb_exc_new_cstr(rb_cPrismCurrentVersionError, current_version)); + const char *ruby_version = RSTRING_PTR(rb_const_get(rb_cObject, rb_intern("RUBY_VERSION"))); + if (!pm_options_version_set(options, ruby_version, 3)) { + rb_exc_raise(rb_exc_new_cstr(rb_cPrismCurrentVersionError, ruby_version)); + } + } else if (RSTRING_LEN(value) == 7 && strncmp(version, "nearest", 7) == 0) { + const char *ruby_version = RSTRING_PTR(rb_const_get(rb_cObject, rb_intern("RUBY_VERSION"))); + const char *nearest_version; + + if (ruby_version[0] < '3' || (ruby_version[0] == '3' && ruby_version[2] < '3')) { + nearest_version = "3.3"; + } else if (ruby_version[0] > '4' || (ruby_version[0] == '4' && ruby_version[2] > '1')) { + nearest_version = "4.1"; + } else { + nearest_version = ruby_version; + } + + if (!pm_options_version_set(options, nearest_version, 3)) { + rb_raise(rb_eArgError, "invalid nearest version: %s", nearest_version); } } else if (!pm_options_version_set(options, version, RSTRING_LEN(value))) { rb_raise(rb_eArgError, "invalid version: %" PRIsVALUE, value); @@ -894,8 +909,10 @@ parse_input(pm_string_t *input, const pm_options_t *options) { * version of Ruby syntax (which you can trigger with `nil` or * `"latest"`). You may also restrict the syntax to a specific version of * Ruby, e.g., with `"3.3.0"`. To parse with the same syntax version that - * the current Ruby is running use `version: "current"`. Raises - * ArgumentError if the version is not currently supported by Prism. + * the current Ruby is running use `version: "current"`. To parse with the + * nearest version to the current Ruby that is running, use + * `version: "nearest"`. Raises ArgumentError if the version is not + * currently supported by Prism. */ static VALUE parse(int argc, VALUE *argv, VALUE self) { diff --git a/prism/options.h b/prism/options.h index c00c7bf7553a4f..9a19a2aeadf31a 100644 --- a/prism/options.h +++ b/prism/options.h @@ -82,7 +82,10 @@ typedef void (*pm_options_shebang_callback_t)(struct pm_options *options, const * parse in the same way as a specific version of CRuby would have. */ typedef enum { - /** If an explicit version is not provided, the current version of prism will be used. */ + /** + * If an explicit version is not provided, the current version of prism will + * be used. + */ PM_OPTIONS_VERSION_UNSET = 0, /** The vendored version of prism in CRuby 3.3.x. */ @@ -452,6 +455,9 @@ PRISM_EXPORTED_FUNCTION void pm_options_free(pm_options_t *options); * | ----- | ------------------------- | * | `0` | use the latest version of prism | * | `1` | use the version of prism that is vendored in CRuby 3.3.0 | + * | `2` | use the version of prism that is vendored in CRuby 3.4.0 | + * | `3` | use the version of prism that is vendored in CRuby 4.0.0 | + * | `4` | use the version of prism that is vendored in CRuby 4.1.0 | * * Each scope is laid out as follows: * From 40e3e43b9380aa339b16471c7e9d9de6894f5ab3 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Tue, 16 Dec 2025 20:38:15 +0000 Subject: [PATCH 10/11] [ruby/net-http] [DOC] Fix links in requests.rb https://github.com/ruby/net-http/commit/a232aea2fc --- lib/net/http/requests.rb | 48 ++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/lib/net/http/requests.rb b/lib/net/http/requests.rb index 939d413f91961c..8dc79a9f665d52 100644 --- a/lib/net/http/requests.rb +++ b/lib/net/http/requests.rb @@ -19,9 +19,9 @@ # # - Request body: optional. # - Response body: yes. -# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: yes. -# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes. -# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: yes. +# - {Safe}[https://en.wikipedia.org/wiki/HTTP#Safe_method]: yes. +# - {Idempotent}[https://en.wikipedia.org/wiki/HTTP#Idempotent_method]: yes. +# - {Cacheable}[https://en.wikipedia.org/wiki/HTTP#Cacheable_method]: yes. # # Related: # @@ -52,9 +52,9 @@ class Net::HTTP::Get < Net::HTTPRequest # # - Request body: optional. # - Response body: no. -# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: yes. -# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes. -# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: yes. +# - {Safe}[https://en.wikipedia.org/wiki/HTTP#Safe_method]: yes. +# - {Idempotent}[https://en.wikipedia.org/wiki/HTTP#Idempotent_method]: yes. +# - {Cacheable}[https://en.wikipedia.org/wiki/HTTP#Cacheable_method]: yes. # # Related: # @@ -87,9 +87,9 @@ class Net::HTTP::Head < Net::HTTPRequest # # - Request body: yes. # - Response body: yes. -# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: no. -# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: no. -# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: yes. +# - {Safe}[https://en.wikipedia.org/wiki/HTTP#Safe_method]: no. +# - {Idempotent}[https://en.wikipedia.org/wiki/HTTP#Idempotent_method]: no. +# - {Cacheable}[https://en.wikipedia.org/wiki/HTTP#Cacheable_method]: yes. # # Related: # @@ -123,9 +123,9 @@ class Net::HTTP::Post < Net::HTTPRequest # # - Request body: yes. # - Response body: yes. -# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: no. -# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes. -# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no. +# - {Safe}[https://en.wikipedia.org/wiki/HTTP#Safe_method]: no. +# - {Idempotent}[https://en.wikipedia.org/wiki/HTTP#Idempotent_method]: yes. +# - {Cacheable}[https://en.wikipedia.org/wiki/HTTP#Cacheable_method]: no. # # Related: # @@ -157,9 +157,9 @@ class Net::HTTP::Put < Net::HTTPRequest # # - Request body: optional. # - Response body: yes. -# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: no. -# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes. -# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no. +# - {Safe}[https://en.wikipedia.org/wiki/HTTP#Safe_method]: no. +# - {Idempotent}[https://en.wikipedia.org/wiki/HTTP#Idempotent_method]: yes. +# - {Cacheable}[https://en.wikipedia.org/wiki/HTTP#Cacheable_method]: no. # # Related: # @@ -189,9 +189,9 @@ class Net::HTTP::Delete < Net::HTTPRequest # # - Request body: optional. # - Response body: yes. -# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: yes. -# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes. -# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no. +# - {Safe}[https://en.wikipedia.org/wiki/HTTP#Safe_method]: yes. +# - {Idempotent}[https://en.wikipedia.org/wiki/HTTP#Idempotent_method]: yes. +# - {Cacheable}[https://en.wikipedia.org/wiki/HTTP#Cacheable_method]: no. # # Related: # @@ -221,9 +221,9 @@ class Net::HTTP::Options < Net::HTTPRequest # # - Request body: no. # - Response body: yes. -# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: yes. -# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes. -# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no. +# - {Safe}[https://en.wikipedia.org/wiki/HTTP#Safe_method]: yes. +# - {Idempotent}[https://en.wikipedia.org/wiki/HTTP#Idempotent_method]: yes. +# - {Cacheable}[https://en.wikipedia.org/wiki/HTTP#Cacheable_method]: no. # # Related: # @@ -256,9 +256,9 @@ class Net::HTTP::Trace < Net::HTTPRequest # # - Request body: yes. # - Response body: yes. -# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: no. -# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: no. -# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no. +# - {Safe}[https://en.wikipedia.org/wiki/HTTP#Safe_method]: no. +# - {Idempotent}[https://en.wikipedia.org/wiki/HTTP#Idempotent_method]: no. +# - {Cacheable}[https://en.wikipedia.org/wiki/HTTP#Cacheable_method]: no. # # Related: # From 6f16e87ff06c412edc0cac334b3c078aa0fafa22 Mon Sep 17 00:00:00 2001 From: Masafumi Koba <473530+ybiquitous@users.noreply.github.com> Date: Fri, 16 Jan 2026 18:23:21 +0900 Subject: [PATCH 11/11] [ruby/open-uri] Improve URI.open documentation with usage example This improves the `URI.open` method documentation by adding a code example requiring `open-uri` as a basic usage. When reading the current documentation first, I didn't realize that `open-uri` was required to call the method. I believe the improved version could be more helpful for new users. ```sh-session $ ruby -r uri -e 'p URI.open("http://example.com")' -e:1:in '
': private method 'open' called for module URI (NoMethodError) ``` Ref https://docs.ruby-lang.org/en/master/URI.html#method-c-open Also, this improves formatting with code fonts for better readability. https://github.com/ruby/open-uri/commit/f4400edc27 --- lib/open-uri.rb | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/open-uri.rb b/lib/open-uri.rb index 5983c7368b1d35..844865b13ac0a1 100644 --- a/lib/open-uri.rb +++ b/lib/open-uri.rb @@ -4,22 +4,25 @@ require 'time' module URI - # Allows the opening of various resources including URIs. + # Allows the opening of various resources including URIs. Example: # - # If the first argument responds to the 'open' method, 'open' is called on + # require "open-uri" + # URI.open("http://example.com") { |f| f.read } + # + # If the first argument responds to the +open+ method, +open+ is called on # it with the rest of the arguments. # # If the first argument is a string that begins with (protocol)://, it is parsed by - # URI.parse. If the parsed object responds to the 'open' method, - # 'open' is called on it with the rest of the arguments. + # URI.parse. If the parsed object responds to the +open+ method, + # +open+ is called on it with the rest of the arguments. # # Otherwise, Kernel#open is called. # # OpenURI::OpenRead#open provides URI::HTTP#open, URI::HTTPS#open and # URI::FTP#open, Kernel#open. # - # We can accept URIs and strings that begin with http://, https:// and - # ftp://. In these cases, the opened file object is extended by OpenURI::Meta. + # We can accept URIs and strings that begin with http://, https:// and + # ftp://. In these cases, the opened file object is extended by OpenURI::Meta. def self.open(name, *rest, &block) if name.respond_to?(:open) name.open(*rest, &block)