counting_semaphore 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -17,11 +17,6 @@ class RedisSemaphoreTest < Minitest::Test
17
17
  end
18
18
  end
19
19
 
20
- def test_initializes_with_correct_capacity
21
- semaphore = CountingSemaphore::RedisSemaphore.new(5, "test_namespace")
22
- assert_equal 5, semaphore.instance_variable_get(:@capacity)
23
- end
24
-
25
20
  def test_capacity_attribute_returns_the_initialized_capacity
26
21
  semaphore = CountingSemaphore::RedisSemaphore.new(10, "test_namespace")
27
22
  assert_equal 10, semaphore.capacity
@@ -158,7 +153,7 @@ class RedisSemaphoreTest < Minitest::Test
158
153
  results << "client2_attempting_5_tokens"
159
154
  end
160
155
 
161
- semaphore2.with_lease(5, timeout_seconds: 2) do
156
+ semaphore2.with_lease(5, timeout: 2) do
162
157
  results << "client2_acquired_5_tokens"
163
158
  sleep 0.05
164
159
  results << "client2_releasing_5_tokens"
@@ -264,7 +259,7 @@ class RedisSemaphoreTest < Minitest::Test
264
259
  )
265
260
 
266
261
  begin
267
- semaphore2.with_lease(1, timeout_seconds: 0.5) do
262
+ semaphore2.with_lease(1, timeout: 0.5) do
268
263
  # This should not execute
269
264
  end
270
265
  rescue CountingSemaphore::LeaseTimeout
@@ -326,7 +321,7 @@ class RedisSemaphoreTest < Minitest::Test
326
321
  )
327
322
 
328
323
  begin
329
- semaphore2.with_lease(1, timeout_seconds: 0.5) do
324
+ semaphore2.with_lease(1, timeout: 0.5) do
330
325
  # This should not execute
331
326
  end
332
327
  rescue CountingSemaphore::LeaseTimeout => e
@@ -401,7 +396,7 @@ class RedisSemaphoreTest < Minitest::Test
401
396
  )
402
397
 
403
398
  begin
404
- semaphore2.with_lease(timeout_seconds: 0.5) do # Uses default token count of 1
399
+ semaphore2.with_lease(timeout: 0.5) do # Uses default token count of 1
405
400
  # This should not execute
406
401
  end
407
402
  rescue CountingSemaphore::LeaseTimeout
@@ -417,6 +412,37 @@ class RedisSemaphoreTest < Minitest::Test
417
412
  assert client2_timeout_raised, "Expected client2 to raise LeaseTimeout but it didn't"
418
413
  end
419
414
 
415
+ def test_with_lease_yields_lease_to_block
416
+ namespace = "test_semaphore_#{SecureRandom.uuid}"
417
+ semaphore = CountingSemaphore::RedisSemaphore.new(5, namespace, redis: Redis.new(db: REDIS_DB))
418
+ yielded_lease = nil
419
+
420
+ result = semaphore.with_lease(3) do |lease|
421
+ yielded_lease = lease
422
+ "block result"
423
+ end
424
+
425
+ assert_equal "block result", result
426
+ refute_nil yielded_lease
427
+ assert_instance_of CountingSemaphore::Lease, yielded_lease
428
+ assert_equal 3, yielded_lease.permits
429
+ assert_equal semaphore, yielded_lease.semaphore
430
+ assert_equal 5, semaphore.available_permits
431
+ end
432
+
433
+ def test_with_lease_yields_nil_for_zero_permits
434
+ namespace = "test_semaphore_#{SecureRandom.uuid}"
435
+ semaphore = CountingSemaphore::RedisSemaphore.new(5, namespace, redis: Redis.new(db: REDIS_DB))
436
+ yielded_lease = :not_set
437
+
438
+ semaphore.with_lease(0) do |lease|
439
+ yielded_lease = lease
440
+ end
441
+
442
+ assert_nil yielded_lease
443
+ assert_equal 5, semaphore.available_permits
444
+ end
445
+
420
446
  def test_currently_leased_returns_zero_initially
421
447
  semaphore = CountingSemaphore::RedisSemaphore.new(5, "test_namespace")
422
448
  assert_equal 0, semaphore.currently_leased
@@ -483,4 +509,392 @@ class RedisSemaphoreTest < Minitest::Test
483
509
  assert_equal 0, semaphore1.currently_leased
484
510
  assert_equal 0, semaphore2.currently_leased
485
511
  end
512
+
513
+ # Tests for concurrent-ruby compatible API
514
+
515
+ def test_acquire_and_release_single_permit
516
+ namespace = "test_semaphore_#{SecureRandom.uuid}"
517
+ semaphore = CountingSemaphore::RedisSemaphore.new(3, namespace, redis: Redis.new(db: REDIS_DB))
518
+
519
+ assert_equal 3, semaphore.available_permits
520
+
521
+ lease = semaphore.acquire(1)
522
+ assert_equal 2, semaphore.available_permits
523
+ semaphore.release(lease)
524
+ assert_equal 3, semaphore.available_permits
525
+ end
526
+
527
+ def test_acquire_and_release_multiple_permits
528
+ namespace = "test_semaphore_#{SecureRandom.uuid}"
529
+ semaphore = CountingSemaphore::RedisSemaphore.new(5, namespace, redis: Redis.new(db: REDIS_DB))
530
+
531
+ lease = semaphore.acquire(3)
532
+ assert_equal 2, semaphore.available_permits
533
+ semaphore.release(lease)
534
+ assert_equal 5, semaphore.available_permits
535
+ end
536
+
537
+ def test_acquire_defaults_to_one_permit
538
+ namespace = "test_semaphore_#{SecureRandom.uuid}"
539
+ semaphore = CountingSemaphore::RedisSemaphore.new(3, namespace, redis: Redis.new(db: REDIS_DB))
540
+
541
+ lease = semaphore.acquire
542
+ assert_equal 2, semaphore.available_permits
543
+
544
+ semaphore.release(lease)
545
+ assert_equal 3, semaphore.available_permits
546
+ end
547
+
548
+ test_with_timeout "acquire blocks until permits available" do
549
+ namespace = "test_semaphore_#{SecureRandom.uuid}"
550
+ completed_order = []
551
+ mutex = Mutex.new
552
+
553
+ # Thread 1: Acquires permits and holds them
554
+ thread1 = Thread.new do
555
+ semaphore1 = CountingSemaphore::RedisSemaphore.new(2, namespace, redis: Redis.new(db: REDIS_DB))
556
+ lease1 = semaphore1.acquire(2)
557
+ mutex.synchronize { completed_order << :thread1_acquired }
558
+ sleep(0.2) # Hold briefly
559
+ semaphore1.release(lease1)
560
+ mutex.synchronize { completed_order << :thread1_released }
561
+ end
562
+
563
+ sleep(0.1) # Let thread1 acquire
564
+
565
+ # Thread 2: Tries to acquire - should block until thread1 releases
566
+ thread2 = Thread.new do
567
+ semaphore2 = CountingSemaphore::RedisSemaphore.new(2, namespace, redis: Redis.new(db: REDIS_DB))
568
+ mutex.synchronize { completed_order << :thread2_attempting }
569
+ lease2 = semaphore2.acquire(1)
570
+ mutex.synchronize { completed_order << :thread2_acquired }
571
+ semaphore2.release(lease2)
572
+ end
573
+
574
+ thread1.join
575
+ thread2.join
576
+
577
+ # Thread2 should acquire after thread1 releases
578
+ assert_equal [:thread1_acquired, :thread2_attempting, :thread1_released, :thread2_acquired], completed_order
579
+ end
580
+
581
+ def test_acquire_raises_error_for_invalid_permits
582
+ namespace = "test_semaphore_#{SecureRandom.uuid}"
583
+ semaphore = CountingSemaphore::RedisSemaphore.new(5, namespace, redis: Redis.new(db: REDIS_DB))
584
+
585
+ assert_raises(ArgumentError) do
586
+ semaphore.acquire(0)
587
+ end
588
+
589
+ assert_raises(ArgumentError) do
590
+ semaphore.acquire(-1)
591
+ end
592
+ end
593
+
594
+ def test_acquire_raises_error_for_permits_exceeding_capacity
595
+ namespace = "test_semaphore_#{SecureRandom.uuid}"
596
+ semaphore = CountingSemaphore::RedisSemaphore.new(3, namespace, redis: Redis.new(db: REDIS_DB))
597
+
598
+ assert_raises(ArgumentError) do
599
+ semaphore.acquire(5)
600
+ end
601
+ end
602
+
603
+ def test_release_raises_error_for_invalid_lease
604
+ namespace = "test_semaphore_#{SecureRandom.uuid}"
605
+ semaphore = CountingSemaphore::RedisSemaphore.new(5, namespace, redis: Redis.new(db: REDIS_DB))
606
+
607
+ assert_raises(NoMethodError) do
608
+ semaphore.release("not a lease")
609
+ end
610
+
611
+ assert_raises(NoMethodError) do
612
+ semaphore.release(nil)
613
+ end
614
+ end
615
+
616
+ def test_release_raises_error_for_lease_from_different_semaphore
617
+ namespace1 = "test_semaphore_#{SecureRandom.uuid}"
618
+ namespace2 = "test_semaphore_#{SecureRandom.uuid}"
619
+ semaphore1 = CountingSemaphore::RedisSemaphore.new(5, namespace1, redis: Redis.new(db: REDIS_DB))
620
+ semaphore2 = CountingSemaphore::RedisSemaphore.new(5, namespace2, redis: Redis.new(db: REDIS_DB))
621
+
622
+ lease = semaphore1.acquire(1)
623
+
624
+ assert_raises(ArgumentError) do
625
+ semaphore2.release(lease)
626
+ end
627
+
628
+ semaphore1.release(lease)
629
+ end
630
+
631
+ def test_try_acquire_succeeds_when_permits_available
632
+ namespace = "test_semaphore_#{SecureRandom.uuid}"
633
+ semaphore = CountingSemaphore::RedisSemaphore.new(3, namespace, redis: Redis.new(db: REDIS_DB))
634
+
635
+ lease = semaphore.try_acquire(2)
636
+ refute_nil lease
637
+ assert_equal 1, semaphore.available_permits
638
+
639
+ semaphore.release(lease)
640
+ end
641
+
642
+ def test_try_acquire_fails_when_permits_not_available
643
+ namespace = "test_semaphore_#{SecureRandom.uuid}"
644
+ semaphore = CountingSemaphore::RedisSemaphore.new(2, namespace, redis: Redis.new(db: REDIS_DB))
645
+
646
+ lease1 = semaphore.acquire(2)
647
+ lease2 = semaphore.try_acquire(1)
648
+ assert_nil lease2
649
+
650
+ semaphore.release(lease1)
651
+ end
652
+
653
+ def test_try_acquire_with_nil_timeout_returns_quickly
654
+ namespace = "test_semaphore_#{SecureRandom.uuid}"
655
+ semaphore = CountingSemaphore::RedisSemaphore.new(1, namespace, redis: Redis.new(db: REDIS_DB))
656
+
657
+ lease1 = semaphore.acquire(1)
658
+ start_time = Time.now
659
+
660
+ lease2 = semaphore.try_acquire(1, timeout: nil)
661
+ elapsed_time = Time.now - start_time
662
+
663
+ assert_nil lease2
664
+ assert elapsed_time < 0.5, "Should return quickly, took #{elapsed_time}s"
665
+
666
+ semaphore.release(lease1)
667
+ end
668
+
669
+ test_with_timeout "try_acquire with timeout waits for permits" do
670
+ namespace = "test_semaphore_#{SecureRandom.uuid}"
671
+
672
+ # Thread 1: Holds the permit temporarily
673
+ thread1 = Thread.new do
674
+ semaphore1 = CountingSemaphore::RedisSemaphore.new(1, namespace, redis: Redis.new(db: REDIS_DB))
675
+ lease1 = semaphore1.acquire(1)
676
+ sleep(0.3)
677
+ semaphore1.release(lease1)
678
+ end
679
+
680
+ sleep(0.1) # Let thread1 acquire
681
+
682
+ # Thread 2: Tries to acquire with timeout - should eventually succeed
683
+ lease2 = nil
684
+ elapsed_time = 0
685
+ thread2 = Thread.new do
686
+ semaphore2 = CountingSemaphore::RedisSemaphore.new(1, namespace, redis: Redis.new(db: REDIS_DB))
687
+ start_time = Time.now
688
+ lease2 = semaphore2.try_acquire(1, timeout: 1.0)
689
+ elapsed_time = Time.now - start_time
690
+ semaphore2.release(lease2) if lease2
691
+ end
692
+
693
+ thread1.join
694
+ thread2.join
695
+
696
+ refute_nil lease2
697
+ assert elapsed_time >= 0.2, "Should have waited for release"
698
+ assert elapsed_time < 1.0, "Should not have waited full timeout"
699
+ end
700
+
701
+ def test_try_acquire_with_timeout_fails_when_timeout_exceeded
702
+ namespace = "test_semaphore_#{SecureRandom.uuid}"
703
+ semaphore = CountingSemaphore::RedisSemaphore.new(1, namespace, redis: Redis.new(db: REDIS_DB))
704
+
705
+ lease1 = semaphore.acquire(1)
706
+ start_time = Time.now
707
+ lease2 = semaphore.try_acquire(1, timeout: 0.3)
708
+ elapsed_time = Time.now - start_time
709
+
710
+ assert_nil lease2
711
+ assert elapsed_time >= 0.3, "Should have waited for timeout"
712
+
713
+ semaphore.release(lease1)
714
+ end
715
+
716
+ def test_try_acquire_defaults_to_one_permit
717
+ namespace = "test_semaphore_#{SecureRandom.uuid}"
718
+ semaphore = CountingSemaphore::RedisSemaphore.new(3, namespace, redis: Redis.new(db: REDIS_DB))
719
+
720
+ lease = semaphore.try_acquire
721
+ refute_nil lease
722
+ assert_equal 2, semaphore.available_permits
723
+
724
+ semaphore.release(lease)
725
+ end
726
+
727
+ def test_try_acquire_raises_error_for_invalid_permits
728
+ namespace = "test_semaphore_#{SecureRandom.uuid}"
729
+ semaphore = CountingSemaphore::RedisSemaphore.new(5, namespace, redis: Redis.new(db: REDIS_DB))
730
+
731
+ assert_raises(ArgumentError) do
732
+ semaphore.try_acquire(0)
733
+ end
734
+
735
+ assert_raises(ArgumentError) do
736
+ semaphore.try_acquire(-1)
737
+ end
738
+ end
739
+
740
+ def test_available_permits_returns_correct_count
741
+ namespace = "test_semaphore_#{SecureRandom.uuid}"
742
+ semaphore = CountingSemaphore::RedisSemaphore.new(5, namespace, redis: Redis.new(db: REDIS_DB))
743
+
744
+ assert_equal 5, semaphore.available_permits
745
+
746
+ lease1 = semaphore.acquire(2)
747
+ assert_equal 3, semaphore.available_permits
748
+
749
+ lease2 = semaphore.acquire(1)
750
+ assert_equal 2, semaphore.available_permits
751
+
752
+ # Release all acquired permits
753
+ semaphore.release(lease2)
754
+ semaphore.release(lease1)
755
+ assert_equal 5, semaphore.available_permits
756
+ end
757
+
758
+ test_with_timeout "available_permits with concurrent operations" do
759
+ namespace = "test_semaphore_#{SecureRandom.uuid}"
760
+ semaphore = CountingSemaphore::RedisSemaphore.new(5, namespace, redis: Redis.new(db: REDIS_DB))
761
+
762
+ threads = 3.times.map do
763
+ Thread.new do
764
+ lease = semaphore.acquire(1)
765
+ sleep(0.1)
766
+ semaphore.release(lease)
767
+ end
768
+ end
769
+
770
+ sleep(0.05) # Let threads acquire
771
+ available = semaphore.available_permits
772
+
773
+ # Should have 2 or fewer available (3 threads acquired)
774
+ assert available <= 2, "Expected <= 2 available, got #{available}"
775
+
776
+ threads.each(&:join)
777
+
778
+ # All should be available now
779
+ assert_equal 5, semaphore.available_permits
780
+ end
781
+
782
+ def test_drain_permits_acquires_all_available
783
+ namespace = "test_semaphore_#{SecureRandom.uuid}"
784
+ semaphore = CountingSemaphore::RedisSemaphore.new(5, namespace, redis: Redis.new(db: REDIS_DB))
785
+
786
+ drained_lease = semaphore.drain_permits
787
+
788
+ refute_nil drained_lease
789
+ assert_equal 5, drained_lease.permits
790
+ assert_equal 0, semaphore.available_permits
791
+
792
+ # Release them back
793
+ semaphore.release(drained_lease)
794
+ assert_equal 5, semaphore.available_permits
795
+ end
796
+
797
+ def test_drain_permits_acquires_only_available
798
+ namespace = "test_semaphore_#{SecureRandom.uuid}"
799
+ semaphore = CountingSemaphore::RedisSemaphore.new(5, namespace, redis: Redis.new(db: REDIS_DB))
800
+
801
+ lease1 = semaphore.acquire(2)
802
+ drained_lease = semaphore.drain_permits
803
+
804
+ refute_nil drained_lease
805
+ assert_equal 3, drained_lease.permits
806
+ assert_equal 0, semaphore.available_permits
807
+
808
+ # Release them back
809
+ semaphore.release(drained_lease)
810
+ semaphore.release(lease1)
811
+ assert_equal 5, semaphore.available_permits
812
+ end
813
+
814
+ def test_drain_permits_returns_nil_when_none_available
815
+ namespace = "test_semaphore_#{SecureRandom.uuid}"
816
+ semaphore = CountingSemaphore::RedisSemaphore.new(3, namespace, redis: Redis.new(db: REDIS_DB))
817
+
818
+ lease = semaphore.acquire(3)
819
+ drained_lease = semaphore.drain_permits
820
+
821
+ assert_nil drained_lease
822
+ assert_equal 0, semaphore.available_permits
823
+ semaphore.release(lease)
824
+ end
825
+
826
+ test_with_timeout "acquire release maintain correct count under stress" do
827
+ namespace = "test_semaphore_#{SecureRandom.uuid}"
828
+ semaphore = CountingSemaphore::RedisSemaphore.new(10, namespace, redis: Redis.new(db: REDIS_DB))
829
+ iterations = 10
830
+
831
+ threads = 5.times.map do
832
+ Thread.new do
833
+ iterations.times do
834
+ lease = semaphore.acquire(1)
835
+ # No sleep - stress test
836
+ semaphore.release(lease)
837
+ end
838
+ end
839
+ end
840
+
841
+ threads.each(&:join)
842
+
843
+ # All permits should be available
844
+ assert_equal 10, semaphore.available_permits
845
+ assert_equal 0, semaphore.currently_leased
846
+ end
847
+
848
+ test_with_timeout "concurrent ruby api compatibility pattern" do
849
+ namespace = "test_semaphore_#{SecureRandom.uuid}"
850
+ semaphore = CountingSemaphore::RedisSemaphore.new(3, namespace, redis: Redis.new(db: REDIS_DB))
851
+ results = []
852
+ mutex = Mutex.new
853
+
854
+ threads = 5.times.map do |i|
855
+ Thread.new do
856
+ if (lease = semaphore.try_acquire(1, timeout: 1.0))
857
+ begin
858
+ mutex.synchronize { results << i }
859
+ sleep(0.1)
860
+ ensure
861
+ semaphore.release(lease)
862
+ end
863
+ end
864
+ end
865
+ end
866
+
867
+ threads.each(&:join)
868
+
869
+ # All threads should complete without hanging
870
+ assert results.length > 0, "Expected at least some threads to succeed"
871
+ assert results.length <= 5, "Expected no more threads than total (5)"
872
+ assert_equal 3, semaphore.available_permits
873
+ end
874
+
875
+ test_with_timeout "distributed acquire and release across multiple instances" do
876
+ namespace = "test_semaphore_#{SecureRandom.uuid}"
877
+ semaphore1 = CountingSemaphore::RedisSemaphore.new(5, namespace, redis: Redis.new(db: REDIS_DB))
878
+ semaphore2 = CountingSemaphore::RedisSemaphore.new(5, namespace, redis: Redis.new(db: REDIS_DB))
879
+
880
+ # Acquire from first instance
881
+ lease1 = semaphore1.acquire(2)
882
+ assert_equal 3, semaphore1.available_permits
883
+ assert_equal 3, semaphore2.available_permits
884
+
885
+ # Acquire from second instance
886
+ lease2 = semaphore2.acquire(2)
887
+ assert_equal 1, semaphore1.available_permits
888
+ assert_equal 1, semaphore2.available_permits
889
+
890
+ # Release from first instance
891
+ semaphore1.release(lease1)
892
+ assert_equal 3, semaphore1.available_permits
893
+ assert_equal 3, semaphore2.available_permits
894
+
895
+ # Release from second instance
896
+ semaphore2.release(lease2)
897
+ assert_equal 5, semaphore1.available_permits
898
+ assert_equal 5, semaphore2.available_permits
899
+ end
486
900
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: counting_semaphore
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julik Tarkhanov
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-10-17 00:00:00.000000000 Z
10
+ date: 2025-10-19 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: minitest
@@ -79,6 +79,20 @@ dependencies:
79
79
  - - "~>"
80
80
  - !ruby/object:Gem::Version
81
81
  version: '2.4'
82
+ - !ruby/object:Gem::Dependency
83
+ name: sord
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: 0.0.0
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: 0.0.0
82
96
  description: Provides both local (in-process) and shared (Redis-based) counting semaphores
83
97
  for controlling concurrent access to resources
84
98
  email:
@@ -93,7 +107,6 @@ files:
93
107
  - ".ruby-version"
94
108
  - AGENTS.md
95
109
  - Gemfile
96
- - Gemfile.lock
97
110
  - LICENSE
98
111
  - README.md
99
112
  - Rakefile
@@ -101,8 +114,10 @@ files:
101
114
  - lib/counting_semaphore/local_semaphore.rb
102
115
  - lib/counting_semaphore/null_logger.rb
103
116
  - lib/counting_semaphore/redis_semaphore.rb
104
- - lib/counting_semaphore/shared_semaphore.rb
105
117
  - lib/counting_semaphore/version.rb
118
+ - lib/counting_semaphore/with_lease_support.rb
119
+ - rbi/counting_semaphore.rbi
120
+ - sig/counting_semaphore.rbs
106
121
  - test/counting_semaphore/local_semaphore_test.rb
107
122
  - test/counting_semaphore/redis_semaphore_test.rb
108
123
  - test/test_helper.rb
data/Gemfile.lock DELETED
@@ -1,76 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- counting_semaphore (0.1.0)
5
-
6
- GEM
7
- remote: https://rubygems.org/
8
- specs:
9
- ast (2.4.3)
10
- connection_pool (2.5.4)
11
- json (2.15.1)
12
- language_server-protocol (3.17.0.5)
13
- lint_roller (1.1.0)
14
- minitest (5.26.0)
15
- parallel (1.27.0)
16
- parser (3.3.9.0)
17
- ast (~> 2.4.1)
18
- racc
19
- prism (1.6.0)
20
- racc (1.8.1)
21
- rainbow (3.1.1)
22
- rake (13.3.0)
23
- redis (5.4.1)
24
- redis-client (>= 0.22.0)
25
- redis-client (0.26.1)
26
- connection_pool
27
- regexp_parser (2.11.3)
28
- rubocop (1.80.2)
29
- json (~> 2.3)
30
- language_server-protocol (~> 3.17.0.2)
31
- lint_roller (~> 1.1.0)
32
- parallel (~> 1.10)
33
- parser (>= 3.3.0.2)
34
- rainbow (>= 2.2.2, < 4.0)
35
- regexp_parser (>= 2.9.3, < 3.0)
36
- rubocop-ast (>= 1.46.0, < 2.0)
37
- ruby-progressbar (~> 1.7)
38
- unicode-display_width (>= 2.4.0, < 4.0)
39
- rubocop-ast (1.47.1)
40
- parser (>= 3.3.7.2)
41
- prism (~> 1.4)
42
- rubocop-performance (1.25.0)
43
- lint_roller (~> 1.1)
44
- rubocop (>= 1.75.0, < 2.0)
45
- rubocop-ast (>= 1.38.0, < 2.0)
46
- ruby-progressbar (1.13.0)
47
- standard (1.51.1)
48
- language_server-protocol (~> 3.17.0.2)
49
- lint_roller (~> 1.0)
50
- rubocop (~> 1.80.2)
51
- standard-custom (~> 1.0.0)
52
- standard-performance (~> 1.8)
53
- standard-custom (1.0.2)
54
- lint_roller (~> 1.0)
55
- rubocop (~> 1.50)
56
- standard-performance (1.8.0)
57
- lint_roller (~> 1.1)
58
- rubocop-performance (~> 1.25.0)
59
- unicode-display_width (3.2.0)
60
- unicode-emoji (~> 4.1)
61
- unicode-emoji (4.1.0)
62
-
63
- PLATFORMS
64
- arm64-darwin-24
65
- ruby
66
-
67
- DEPENDENCIES
68
- connection_pool (~> 2.4)
69
- counting_semaphore!
70
- minitest (~> 5.0)
71
- rake (~> 13.0)
72
- redis (~> 5.0)
73
- standard (>= 1.35.1)
74
-
75
- BUNDLED WITH
76
- 2.6.9