Skip to content

Commit 54b1092

Browse files
committed
Atomic specs now defined by share_examples_for
wip: Updated atomic_spec to support platform-specific testing.
1 parent 79b5d32 commit 54b1092

File tree

5 files changed

+156
-109
lines changed

5 files changed

+156
-109
lines changed

Rakefile

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ require 'rake'
22
require 'bundler/gem_tasks'
33
require 'rspec'
44
require 'rspec/core/rake_task'
5+
require 'fileutils'
56

67
require_relative 'lib/extension_helper'
78

@@ -20,9 +21,10 @@ end
2021

2122
desc 'Clean up build artifacts'
2223
task :clean do
23-
rm_rf 'pkg/classes'
24-
rm_f 'lib/*.jar'
25-
rm_rf '**/*.{o,so,bundle}'
24+
rm_rf './pkg/classes'
25+
rm_f Dir.glob('./lib/*.jar')
26+
rm_f Dir.glob('./lib/*.{o,so,bundle}')
27+
rm_rf './tmp'
2628
end
2729

2830
if defined?(JRUBY_VERSION)
@@ -46,7 +48,7 @@ if defined?(JRUBY_VERSION)
4648

4749
task :compile_java => :jar
4850

49-
elsif use_c_extensions?
51+
elsif Concurrent.use_c_extensions?
5052

5153
EXTENSION_NAME = 'concurrent_cruby'
5254

@@ -76,9 +78,9 @@ RSpec::Core::RakeTask.new(:travis_spec) do |t|
7678
end
7779

7880
if defined?(JRUBY_VERSION)
79-
task :default => [:compile_java, :travis_spec]
80-
elsif use_c_extensions?
81-
task :default => [:compile_c, :travis_spec]
81+
task :default => [:clean, :compile_java, :travis_spec]
82+
elsif Concurrent.use_c_extensions?
83+
task :default => [:clean, :compile_c, :travis_spec]
8284
else
8385
task :default => [:clean, :travis_spec]
8486
end

ext/extconf.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
if defined?(JRUBY_VERSION)
77
puts 'JRuby detected. Pure Java optimizations will be used.'
8-
elsif ! use_c_extensions?
8+
elsif ! Concurrent.use_c_extensions?
99
puts 'C optimizations are only supported on MRI 2.0 and above.'
1010
else
1111

lib/extension_helper.rb

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
require 'rbconfig'
22

3-
def use_c_extensions?
4-
host_os = RbConfig::CONFIG['host_os']
5-
ruby_name = RbConfig::CONFIG['ruby_install_name']
6-
(ruby_name =~ /^ruby$/i || host_os =~ /mswin32/i || host_os =~ /mingw32/i) && RUBY_VERSION >= '2.0'
3+
module Concurrent
4+
def self.use_c_extensions?
5+
host_os = RbConfig::CONFIG['host_os']
6+
ruby_name = RbConfig::CONFIG['ruby_install_name']
7+
(ruby_name =~ /^ruby$/i || host_os =~ /mswin32/i || host_os =~ /mingw32/i) && RUBY_VERSION >= '2.0'
8+
end
79
end

spec/concurrent/atomic_spec.rb

Lines changed: 135 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,131 +1,169 @@
11
require 'spec_helper'
22

3-
module Concurrent
3+
share_examples_for :atomic do
44

5-
describe Atomic do
5+
specify :test_construct do
6+
atomic = described_class.new
7+
atomic.value.should be_nil
68

7-
specify :test_construct do
8-
atomic = Atomic.new
9-
atomic.value.should be_nil
9+
atomic = described_class.new(0)
10+
atomic.value.should eq 0
11+
end
1012

11-
atomic = Atomic.new(0)
12-
atomic.value.should eq 0
13-
end
13+
specify :test_value do
14+
atomic = described_class.new(0)
15+
atomic.value = 1
1416

15-
specify :test_value do
16-
atomic = Atomic.new(0)
17-
atomic.value = 1
17+
atomic.value.should eq 1
18+
end
1819

19-
atomic.value.should eq 1
20-
end
20+
specify :test_update do
21+
# use a number outside JRuby's fixnum cache range, to ensure identity is preserved
22+
atomic = described_class.new(1000)
23+
res = atomic.update {|v| v + 1}
2124

22-
specify :test_update do
23-
# use a number outside JRuby's fixnum cache range, to ensure identity is preserved
24-
atomic = Atomic.new(1000)
25-
res = atomic.update {|v| v + 1}
25+
atomic.value.should eq 1001
26+
res.should eq 1001
27+
end
2628

27-
atomic.value.should eq 1001
28-
res.should eq 1001
29-
end
29+
specify :test_try_update do
30+
# use a number outside JRuby's fixnum cache range, to ensure identity is preserved
31+
atomic = described_class.new(1000)
32+
res = atomic.try_update {|v| v + 1}
3033

31-
specify :test_try_update do
32-
# use a number outside JRuby's fixnum cache range, to ensure identity is preserved
33-
atomic = Atomic.new(1000)
34-
res = atomic.try_update {|v| v + 1}
34+
atomic.value.should eq 1001
35+
res.should eq 1001
36+
end
3537

36-
atomic.value.should eq 1001
37-
res.should eq 1001
38-
end
38+
specify :test_swap do
39+
atomic = described_class.new(1000)
40+
res = atomic.swap(1001)
3941

40-
specify :test_swap do
41-
atomic = Atomic.new(1000)
42-
res = atomic.swap(1001)
42+
atomic.value.should eq 1001
43+
res.should eq 1000
44+
end
4345

44-
atomic.value.should eq 1001
45-
res.should eq 1000
46-
end
46+
specify :test_try_update_fails do
47+
# use a number outside JRuby's fixnum cache range, to ensure identity is preserved
48+
atomic = described_class.new(1000)
49+
expect {
50+
# assigning within block exploits implementation detail for test
51+
atomic.try_update{|v| atomic.value = 1001 ; v + 1}
52+
}.to raise_error(Concurrent::Atomic::ConcurrentUpdateError)
53+
end
4754

48-
specify :test_try_update_fails do
49-
# use a number outside JRuby's fixnum cache range, to ensure identity is preserved
50-
atomic = Atomic.new(1000)
51-
expect {
52-
# assigning within block exploits implementation detail for test
53-
atomic.try_update{|v| atomic.value = 1001 ; v + 1}
54-
}.to raise_error(Concurrent::Atomic::ConcurrentUpdateError)
55-
end
55+
specify :test_update_retries do
56+
tries = 0
57+
# use a number outside JRuby's fixnum cache range, to ensure identity is preserved
58+
atomic = described_class.new(1000)
59+
# assigning within block exploits implementation detail for test
60+
atomic.update{|v| tries += 1 ; atomic.value = 1001 ; v + 1}
5661

57-
specify :test_update_retries do
58-
tries = 0
59-
# use a number outside JRuby's fixnum cache range, to ensure identity is preserved
60-
atomic = Atomic.new(1000)
61-
# assigning within block exploits implementation detail for test
62-
atomic.update{|v| tries += 1 ; atomic.value = 1001 ; v + 1}
62+
tries.should eq 2
63+
end
64+
65+
specify :test_numeric_cas do
66+
atomic = described_class.new(0)
6367

64-
tries.should eq 2
68+
# 9-bit idempotent Fixnum (JRuby)
69+
max_8 = 2**256 - 1
70+
min_8 = -(2**256)
71+
72+
atomic.set(max_8)
73+
max_8.upto(max_8 + 2) do |i|
74+
atomic.compare_and_swap(i, i+1).should be_true, "CAS failed for numeric #{i} => #{i + 1}"
6575
end
6676

67-
specify :test_numeric_cas do
68-
atomic = Atomic.new(0)
77+
atomic.set(min_8)
78+
min_8.downto(min_8 - 2) do |i|
79+
atomic.compare_and_swap(i, i-1).should be_true, "CAS failed for numeric #{i} => #{i - 1}"
80+
end
6981

70-
# 9-bit idempotent Fixnum (JRuby)
71-
max_8 = 2**256 - 1
72-
min_8 = -(2**256)
82+
# 64-bit idempotent Fixnum (MRI, Rubinius)
83+
max_64 = 2**62 - 1
84+
min_64 = -(2**62)
7385

74-
atomic.set(max_8)
75-
max_8.upto(max_8 + 2) do |i|
76-
atomic.compare_and_swap(i, i+1).should be_true, "CAS failed for numeric #{i} => #{i + 1}"
77-
end
86+
atomic.set(max_64)
87+
max_64.upto(max_64 + 2) do |i|
88+
atomic.compare_and_swap(i, i+1).should be_true, "CAS failed for numeric #{i} => #{i + 1}"
89+
end
7890

79-
atomic.set(min_8)
80-
min_8.downto(min_8 - 2) do |i|
81-
atomic.compare_and_swap(i, i-1).should be_true, "CAS failed for numeric #{i} => #{i - 1}"
82-
end
91+
atomic.set(min_64)
92+
min_64.downto(min_64 - 2) do |i|
93+
atomic.compare_and_swap(i, i-1).should be_true, "CAS failed for numeric #{i} => #{i - 1}"
94+
end
8395

84-
# 64-bit idempotent Fixnum (MRI, Rubinius)
85-
max_64 = 2**62 - 1
86-
min_64 = -(2**62)
96+
## 64-bit overflow into Bignum (JRuby)
97+
max_64 = 2**63 - 1
98+
min_64 = (-2**63)
8799

88-
atomic.set(max_64)
89-
max_64.upto(max_64 + 2) do |i|
90-
atomic.compare_and_swap(i, i+1).should be_true, "CAS failed for numeric #{i} => #{i + 1}"
91-
end
100+
atomic.set(max_64)
101+
max_64.upto(max_64 + 2) do |i|
102+
atomic.compare_and_swap(i, i+1).should be_true, "CAS failed for numeric #{i} => #{i + 1}"
103+
end
92104

93-
atomic.set(min_64)
94-
min_64.downto(min_64 - 2) do |i|
95-
atomic.compare_and_swap(i, i-1).should be_true, "CAS failed for numeric #{i} => #{i - 1}"
96-
end
105+
atomic.set(min_64)
106+
min_64.downto(min_64 - 2) do |i|
107+
atomic.compare_and_swap(i, i-1).should be_true, "CAS failed for numeric #{i} => #{i - 1}"
108+
end
97109

98-
## 64-bit overflow into Bignum (JRuby)
99-
max_64 = 2**63 - 1
100-
min_64 = (-2**63)
110+
# non-idempotent Float (JRuby, Rubinius, MRI < 2.0.0 or 32-bit)
111+
atomic.set(1.0 + 0.1)
112+
atomic.compare_and_set(1.0 + 0.1, 1.2).should be_true, "CAS failed for #{1.0 + 0.1} => 1.2"
101113

102-
atomic.set(max_64)
103-
max_64.upto(max_64 + 2) do |i|
104-
atomic.compare_and_swap(i, i+1).should be_true, "CAS failed for numeric #{i} => #{i + 1}"
105-
end
114+
# Bignum
115+
atomic.set(2**100)
116+
atomic.compare_and_set(2**100, 0).should be_true, "CAS failed for #{2**100} => 0"
106117

107-
atomic.set(min_64)
108-
min_64.downto(min_64 - 2) do |i|
109-
atomic.compare_and_swap(i, i-1).should be_true, "CAS failed for numeric #{i} => #{i - 1}"
110-
end
118+
# Rational
119+
require 'rational' unless ''.respond_to? :to_r
120+
atomic.set(Rational(1,3))
121+
atomic.compare_and_set(Rational(1,3), 0).should be_true, "CAS failed for #{Rational(1,3)} => 0"
111122

112-
# non-idempotent Float (JRuby, Rubinius, MRI < 2.0.0 or 32-bit)
113-
atomic.set(1.0 + 0.1)
114-
atomic.compare_and_set(1.0 + 0.1, 1.2).should be_true, "CAS failed for #{1.0 + 0.1} => 1.2"
123+
# Complex
124+
require 'complex' unless ''.respond_to? :to_c
125+
atomic.set(Complex(1,2))
126+
atomic.compare_and_set(Complex(1,2), 0).should be_true, "CAS failed for #{Complex(1,2)} => 0"
127+
end
128+
end
129+
130+
module Concurrent
115131

116-
# Bignum
117-
atomic.set(2**100)
118-
atomic.compare_and_set(2**100, 0).should be_true, "CAS failed for #{2**100} => 0"
132+
describe Atomic do
133+
it_should_behave_like :atomic
134+
end
119135

120-
# Rational
121-
require 'rational' unless ''.respond_to? :to_r
122-
atomic.set(Rational(1,3))
123-
atomic.compare_and_set(Rational(1,3), 0).should be_true, "CAS failed for #{Rational(1,3)} => 0"
136+
if defined? Concurrent::CAtomic
137+
describe CAtomic do
138+
it_should_behave_like :atomic
139+
end
140+
elsif defined? Concurrent::JavaAtomic
141+
describe JavaAtomic do
142+
it_should_behave_like :atomic
143+
end
144+
elsif defined? Concurrent::RbxAtomic
145+
describe RbxAtomic do
146+
it_should_behave_like :atomic
147+
end
148+
end
124149

125-
# Complex
126-
require 'complex' unless ''.respond_to? :to_c
127-
atomic.set(Complex(1,2))
128-
atomic.compare_and_set(Complex(1,2), 0).should be_true, "CAS failed for #{Complex(1,2)} => 0"
150+
describe Atomic do
151+
if TestHelpers.use_c_extensions?
152+
it 'inherits from CAtomic' do
153+
Atomic.ancestors.should include(CAtomic)
154+
end
155+
elsif TestHelpers.jruby?
156+
it 'inherits from JavaAtomic' do
157+
Atomic.ancestors.should include(JavaAtomic)
158+
end
159+
elsif TestHelpers.rbx?
160+
it 'inherits from RbxAtomic' do
161+
Atomic.ancestors.should include(RbxAtomic)
162+
end
163+
else
164+
it 'inherits from MutexAtomic' do
165+
Atomic.ancestors.should include(MutexAtomic)
166+
end
129167
end
130168
end
131169
end

spec/support/example_group_extensions.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
require 'rbconfig'
2+
require_relative '../../lib/extension_helper.rb'
23

34
module Concurrent
45
module TestHelpers
@@ -22,6 +23,10 @@ def rbx?
2223
RbConfig::CONFIG['ruby_install_name']=~ /^rbx$/i
2324
end
2425

26+
def use_c_extensions?
27+
Concurrent.use_c_extensions? # from extension_helper.rb
28+
end
29+
2530
def do_no_reset!
2631
@do_not_reset = true
2732
end

0 commit comments

Comments
 (0)