closure_tree 4.5.0 → 4.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/.travis.yml +11 -3
  4. data/CHANGELOG.md +13 -0
  5. data/Gemfile +13 -0
  6. data/README.md +61 -12
  7. data/closure_tree.gemspec +4 -5
  8. data/gemfiles/activerecord_3.2.gemfile +12 -0
  9. data/gemfiles/activerecord_4.0.gemfile +12 -0
  10. data/gemfiles/activerecord_4.1.gemfile +12 -0
  11. data/gemfiles/activerecord_edge.gemfile +12 -0
  12. data/lib/closure_tree/acts_as_tree.rb +2 -9
  13. data/lib/closure_tree/deterministic_ordering.rb +4 -0
  14. data/lib/closure_tree/finders.rb +4 -4
  15. data/lib/closure_tree/hash_tree.rb +1 -1
  16. data/lib/closure_tree/hierarchy_maintenance.rb +19 -6
  17. data/lib/closure_tree/model.rb +2 -10
  18. data/lib/closure_tree/numeric_deterministic_ordering.rb +53 -36
  19. data/lib/closure_tree/numeric_order_support.rb +20 -13
  20. data/lib/closure_tree/support.rb +8 -0
  21. data/lib/closure_tree/support_attributes.rb +10 -0
  22. data/lib/closure_tree/test/matcher.rb +85 -0
  23. data/lib/closure_tree/version.rb +1 -1
  24. data/lib/closure_tree.rb +14 -1
  25. data/spec/cache_invalidation_spec.rb +39 -0
  26. data/spec/{support → db}/models.rb +6 -0
  27. data/spec/db/schema.rb +18 -0
  28. data/spec/label_spec.rb +171 -46
  29. data/spec/matcher_spec.rb +32 -0
  30. data/spec/parallel_spec.rb +85 -49
  31. data/spec/spec_helper.rb +6 -96
  32. data/spec/support/database.rb +49 -0
  33. data/spec/support/database_cleaner.rb +14 -0
  34. data/spec/support/deprecated/attr_accessible.rb +5 -0
  35. data/spec/support/hash_monkey_patch.rb +13 -0
  36. data/spec/support/helpers.rb +8 -0
  37. data/spec/support/sqlite3_with_advisory_lock.rb +10 -0
  38. data/tests.sh +7 -2
  39. metadata +31 -43
  40. data/spec/parallel_prepend_sibling_spec.rb +0 -42
@@ -1,85 +1,99 @@
1
1
  require 'spec_helper'
2
+ require 'securerandom'
2
3
 
3
-
4
- class DbThread
5
- def initialize(&block)
4
+ class WorkerBase
5
+ def initialize(target, run_at, name)
6
+ @target = target
6
7
  @thread = Thread.new do
7
- ActiveRecord::Base.connection_pool.with_connection(&block)
8
+ ActiveRecord::Base.connection_pool.with_connection do
9
+ before_work
10
+ sleep((run_at - Time.now).to_f)
11
+ do_work(name)
12
+ end
8
13
  end
9
14
  end
10
15
 
16
+ def before_work
17
+ end
18
+
19
+ def work(name)
20
+ raise
21
+ end
22
+
11
23
  def join
12
24
  @thread.join
13
25
  end
14
26
  end
15
27
 
16
- describe "threadhot", concurrency: true do
17
-
18
- before :each do
19
- @parent = nil
20
- @iterations = 3
21
- @workers = 10
22
- @min_sleep_time = 0.3
23
- @lock = Mutex.new
24
- @wake_times = []
25
- DatabaseCleaner.clean
28
+ class FindOrCreateWorker < WorkerBase
29
+ def do_work(name)
30
+ (@target || Tag).find_or_create_by_path([name.to_s, :a, :b, :c])
26
31
  end
32
+ end
27
33
 
28
- after :each do
29
- DatabaseCleaner.clean
30
- end
34
+ describe 'Concurrent creation', if: support_concurrency do
31
35
 
32
- def find_or_create_at_same_time(name)
33
- @lock.synchronize { @wake_times << Time.now.to_f + @min_sleep_time }
34
- while @wake_times.size < @workers
35
- sleep(0.1)
36
- end
37
- max_wait_time = @lock.synchronize { @wake_times.max }
38
- sleep_time = max_wait_time - Time.now.to_f
39
- sleep(sleep_time)
40
- (@parent || Tag).find_or_create_by_path([name.to_s, :a, :b, :c])
36
+ before :each do
37
+ @target = nil
38
+ @iterations = 5
39
+ @threads = 10
41
40
  end
42
41
 
43
- def run_workers
44
- @names = []
45
- @iterations.times.each do |iter|
46
- name = "iteration ##{iter}"
47
- @names << name
48
- threads = @workers.times.map do
49
- DbThread.new { find_or_create_at_same_time(name) }
42
+ def run_workers(worker_class = FindOrCreateWorker)
43
+ all_workers = []
44
+ @names = @iterations.times.map { |iter| "iteration ##{iter}" }
45
+ @names.each do |name|
46
+ wake_time = 1.second.from_now
47
+ workers = @threads.times.map do
48
+ worker_class.new(@target, wake_time, name)
50
49
  end
51
- threads.each { |ea| ea.join }
52
- @wake_times.clear
50
+ workers.each(&:join)
51
+ all_workers += workers
52
+ puts name
53
53
  end
54
+ # Ensure we're still connected:
55
+ ActiveRecord::Base.connection_pool.connection
56
+ all_workers
54
57
  end
55
58
 
56
- it "class method will not create dupes" do
59
+ it 'will not create dupes from class methods' do
57
60
  run_workers
58
61
  Tag.roots.collect { |ea| ea.name }.should =~ @names
59
62
  # No dupe children:
60
63
  %w(a b c).each do |ea|
61
- Tag.where(:name => ea).size.should == @iterations
64
+ Tag.where(name: ea).size.should == @iterations
62
65
  end
63
66
  end
64
67
 
65
- it "instance method will not create dupes" do
66
- @parent = Tag.create!(:name => "root")
68
+ it 'will not create dupes from instance methods' do
69
+ @target = Tag.create!(name: 'root')
67
70
  run_workers
68
- @parent.reload.children.collect { |ea| ea.name }.should =~ @names
69
- Tag.where(:name => @names).size.should == @iterations
71
+ @target.reload.children.collect { |ea| ea.name }.should =~ @names
72
+ Tag.where(name: @names).size.should == @iterations
70
73
  %w(a b c).each do |ea|
71
- Tag.where(:name => ea).size.should == @iterations
74
+ Tag.where(name: ea).size.should == @iterations
72
75
  end
73
76
  end
74
77
 
75
- it "creates dupe roots without advisory locks" do
78
+ it 'creates dupe roots without advisory locks' do
76
79
  # disable with_advisory_lock:
77
- Tag.stub(:with_advisory_lock).and_return { |lock_name, &block| block.call }
80
+ Tag.stub(:with_advisory_lock).and_return { |_lock_name, &block| block.call }
78
81
  run_workers
79
- Tag.where(:name => @names).size.should > @iterations
82
+ Tag.where(name: @names).size.should > @iterations
80
83
  end
81
84
 
82
- it 'fails to deadlock from parallel sibling churn' do
85
+ class SiblingPrependerWorker < WorkerBase
86
+ def before_work
87
+ @target.reload
88
+ @sibling = Label.new(name: SecureRandom.hex(10))
89
+ end
90
+
91
+ def do_work(name)
92
+ @target.prepend_sibling @sibling
93
+ end
94
+ end
95
+
96
+ xit 'fails to deadlock from parallel sibling churn' do
83
97
  # target should be non-trivially long to maximize time spent in hierarchy maintenance
84
98
  target = Tag.find_or_create_by_path(('a'..'z').to_a + ('A'..'Z').to_a)
85
99
  expected_children = (1..100).to_a.map { |ea| "root ##{ea}" }
@@ -106,7 +120,7 @@ describe "threadhot", concurrency: true do
106
120
  victim_name = children_to_delete.shift
107
121
  if victim_name
108
122
  Tag.transaction do
109
- victim = target.children.where(:name => victim_name).first
123
+ victim = target.children.where(name: victim_name).first
110
124
  victim.destroy
111
125
  deleted_children << victim_name
112
126
  end
@@ -123,14 +137,14 @@ describe "threadhot", concurrency: true do
123
137
  deleted_children.should =~ expected_children
124
138
  end
125
139
 
126
- it "fails to deadlock while simultaneously deleting items from the same hierarchy" do
140
+ xit 'fails to deadlock while simultaneously deleting items from the same hierarchy' do
127
141
  target = User.find_or_create_by_path((1..200).to_a.map { |ea| ea.to_s })
128
142
  to_delete = target.self_and_ancestors.to_a.shuffle.map(&:email)
129
143
  destroyer_threads = @workers.times.map do
130
144
  DbThread.new do
131
145
  until to_delete.empty?
132
146
  email = to_delete.shift
133
- User.transaction { User.where(:email => email).first.destroy } if email
147
+ User.transaction { User.where(email: email).first.destroy } if email
134
148
  end
135
149
  end
136
150
  end
@@ -138,4 +152,26 @@ describe "threadhot", concurrency: true do
138
152
  User.all.should be_empty
139
153
  end
140
154
 
155
+ class SiblingPrependerWorker < WorkerBase
156
+ def before_work
157
+ @target.reload
158
+ @sibling = Label.new(name: SecureRandom.hex(10))
159
+ end
160
+
161
+ def do_work(name)
162
+ @target.prepend_sibling @sibling
163
+ end
164
+ end
165
+
166
+ it 'fails to deadlock from prepending siblings' do
167
+ @target = Label.find_or_create_by_path %w(root parent)
168
+ run_workers(SiblingPrependerWorker)
169
+ children = Label.roots
170
+ uniq_sort_orders = children.collect { |ea| ea.sort_order }.uniq
171
+ children.size.should == uniq_sort_orders.size
172
+
173
+ # The only non-root node should be "root":
174
+ Label.all.select { |ea| ea.root? }.should == [@target.parent]
175
+ end
176
+
141
177
  end
data/spec/spec_helper.rb CHANGED
@@ -1,110 +1,20 @@
1
- $:.unshift(File.dirname(__FILE__) + '/../lib')
2
- plugin_test_dir = File.dirname(__FILE__)
3
-
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib')
4
2
 
5
3
  ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
6
- require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
4
+ require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
7
5
  require 'rspec'
8
6
  require 'active_record'
9
7
  require 'foreigner'
10
8
  require 'database_cleaner'
11
9
  require 'closure_tree'
10
+ require 'closure_tree/test/matcher'
12
11
  require 'tmpdir'
13
-
14
- if ENV['STDOUT_LOGGING']
15
- log = Logger.new(STDOUT)
16
- log.sev_threshold = Logger::DEBUG
17
- ActiveRecord::Base.logger = log
18
- end
19
-
20
-
21
- ENV["DB"] ||= "mysql"
22
- ActiveRecord::Base.table_name_prefix = ENV['DB_PREFIX'].to_s
23
- ActiveRecord::Base.table_name_suffix = ENV['DB_SUFFIX'].to_s
24
-
25
- if ENV['ATTR_ACCESSIBLE'] == '1'
26
- # turn on whitelisted attributes:
27
- ActiveRecord::Base.send(:include, ActiveModel::MassAssignmentSecurity)
28
- end
29
-
30
- ActiveRecord::Base.configurations = YAML::load(ERB.new(IO.read(plugin_test_dir + "/db/database.yml")).result)
31
-
32
- def recreate_db
33
- db_name = ActiveRecord::Base.configurations[ENV["DB"]]["database"]
34
- case ENV['DB']
35
- when 'sqlite'
36
- when 'postgresql'
37
- `psql -c 'DROP DATABASE #{db_name}' -U postgres`
38
- `psql -c 'CREATE DATABASE #{db_name}' -U postgres`
39
- else
40
- `mysql -e 'DROP DATABASE IF EXISTS #{db_name}'`
41
- `mysql -e 'CREATE DATABASE #{db_name}'`
42
- end
43
- ActiveRecord::Base.connection.reconnect!
44
- end
45
-
46
- ActiveRecord::Base.establish_connection(ENV["DB"].to_sym)
47
-
48
- ActiveRecord::Migration.verbose = false
49
- if ENV['NONUKES']
50
- puts 'skipping database creation'
51
- else
52
- Foreigner.load
53
- recreate_db
54
- require 'db/schema'
55
- end
56
- require 'support/models'
57
-
58
- class Hash
59
- def render_from_yield(&block)
60
- inject({}) do |h, entry|
61
- k, v = entry
62
- h[block.call(k)] = if v.is_a?(Hash) then
63
- v.render_from_yield(&block)
64
- else
65
- block.call(v)
66
- end
67
- h
68
- end
69
- end
70
- end
71
-
72
- DB_QUERIES = []
73
-
74
- ActiveRecord::ConnectionAdapters::AbstractAdapter.class_eval do
75
- def log_with_query_append(query, *args, &block)
76
- DB_QUERIES << query
77
- log_without_query_append(query, *args, &block)
78
- end
79
-
80
- alias_method_chain :log, :query_append
81
- end
12
+ require 'timecop'
82
13
 
83
14
  Thread.abort_on_exception = true
84
15
 
85
- DatabaseCleaner.strategy = :truncation
86
-
87
-
88
- def support_concurrency
89
- # SQLite doesn't support parallel writes
90
- !(ENV['DB'] =~ /sqlite/)
91
- end
16
+ Dir['./spec/support/**/*.rb'].sort.each { |f| require f }
92
17
 
93
18
  RSpec.configure do |config|
94
- config.before(:each) do
95
- DatabaseCleaner.start
96
- end
97
- config.after(:each) do
98
- DatabaseCleaner.clean
99
- DB_QUERIES.clear
100
- end
101
- config.before(:all) do
102
- ENV['FLOCK_DIR'] = Dir.mktmpdir
103
- end
104
- config.after(:all) do
105
- FileUtils.remove_entry_secure ENV['FLOCK_DIR']
106
- end
107
- config.filter_run_excluding :concurrency => !support_concurrency
19
+ config.include ClosureTree::Test::Matcher
108
20
  end
109
-
110
-
@@ -0,0 +1,49 @@
1
+ database_folder = "#{File.dirname(__FILE__)}/../db"
2
+ database_adapter = ENV['DB'] ||= 'mysql'
3
+
4
+ if ENV['STDOUT_LOGGING']
5
+ log = Logger.new(STDOUT)
6
+ log.sev_threshold = Logger::DEBUG
7
+ ActiveRecord::Base.logger = log
8
+ end
9
+
10
+ ActiveRecord::Migration.verbose = false
11
+ ActiveRecord::Base.table_name_prefix = ENV['DB_PREFIX'].to_s
12
+ ActiveRecord::Base.table_name_suffix = ENV['DB_SUFFIX'].to_s
13
+ ActiveRecord::Base.configurations = YAML::load(ERB.new(IO.read("#{database_folder}/database.yml")).result)
14
+
15
+ config = ActiveRecord::Base.configurations[database_adapter]
16
+
17
+ unless config['database'] == ':memory:'
18
+ # Postgresql or Mysql
19
+ config['database'].concat ENV['TRAVIS_JOB_NUMBER'].to_s.gsub(/\W/, '_')
20
+ end
21
+
22
+ begin
23
+ case database_adapter
24
+ when 'sqlite'
25
+ ActiveRecord::Base.establish_connection(database_adapter.to_sym)
26
+ when 'mysql'
27
+ ActiveRecord::Base.establish_connection(config.merge('database' => nil))
28
+ ActiveRecord::Base.connection.recreate_database(config['database'], {charset: 'utf8', collation: 'utf8_unicode_ci'})
29
+ when 'postgresql'
30
+ ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public'))
31
+ ActiveRecord::Base.connection.recreate_database(config['database'], config.merge('encoding' => 'utf8'))
32
+ end
33
+ end unless ENV['NONUKES']
34
+
35
+ ActiveRecord::Base.establish_connection(config)
36
+ Foreigner.load
37
+
38
+ require "#{database_folder}/schema"
39
+ require "#{database_folder}/models"
40
+
41
+ # See http://stackoverflow.com/a/22388177/1268016
42
+ def count_queries(&block)
43
+ count = 0
44
+ counter_fn = ->(name, started, finished, unique_id, payload) do
45
+ count += 1 unless payload[:name].in? %w[ CACHE SCHEMA ]
46
+ end
47
+ ActiveSupport::Notifications.subscribed(counter_fn, "sql.active_record", &block)
48
+ count
49
+ end
@@ -0,0 +1,14 @@
1
+ RSpec.configure do |config|
2
+
3
+ DatabaseCleaner.strategy = :truncation
4
+
5
+ config.before(:each) do
6
+ ActiveRecord::Base.connection_pool.connection
7
+ DatabaseCleaner.start
8
+ end
9
+
10
+ config.after(:each) do
11
+ ActiveRecord::Base.connection_pool.connection
12
+ DatabaseCleaner.clean
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ # Delete this file when support for 3.2 is dropped
2
+ if ENV['ATTR_ACCESSIBLE'] == '1'
3
+ # turn on whitelisted attributes:
4
+ ActiveRecord::Base.send(:include, ActiveModel::MassAssignmentSecurity)
5
+ end
@@ -0,0 +1,13 @@
1
+ class Hash
2
+ def render_from_yield(&block)
3
+ reduce({}) do |h, entry|
4
+ k, v = entry
5
+ h[block.call(k)] = if v.is_a?(Hash) then
6
+ v.render_from_yield(&block)
7
+ else
8
+ block.call(v)
9
+ end
10
+ h
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,8 @@
1
+ def sqlite?
2
+ ENV['DB'] =~ /sqlite/
3
+ end
4
+
5
+ def support_concurrency
6
+ # SQLite doesn't support parallel writes
7
+ !sqlite?
8
+ end
@@ -0,0 +1,10 @@
1
+ if sqlite?
2
+ RSpec.configure do |config|
3
+ config.before(:suite) do
4
+ ENV['FLOCK_DIR'] = Dir.mktmpdir
5
+ end
6
+ config.after(:suite) do
7
+ FileUtils.remove_entry_secure ENV['FLOCK_DIR']
8
+ end
9
+ end
10
+ end
data/tests.sh CHANGED
@@ -2,7 +2,12 @@
2
2
 
3
3
  appraisal install
4
4
 
5
- for db in sqlite mysql postgresql
5
+ for RMI in 1.9.3-p429 2.1.2
6
6
  do
7
- DB=$db WITH_ADVISORY_LOCK_PREFIX=$(date +%s) appraisal rake all_spec_flavors
7
+ rbenv local $RMI
8
+ for db in postgresql mysql sqlite
9
+ do
10
+ bundle install --quiet
11
+ DB=$db WITH_ADVISORY_LOCK_PREFIX=$(date +%s) appraisal rake all_spec_flavors
12
+ done
8
13
  done
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: closure_tree
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.5.0
4
+ version: 4.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthew McEachen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-18 00:00:00.000000000 Z
11
+ date: 2014-06-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -70,16 +70,16 @@ dependencies:
70
70
  name: rspec
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - ">="
73
+ - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '0'
75
+ version: 2.14.0
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - ">="
80
+ - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '0'
82
+ version: 2.14.0
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: rspec-instafail
85
85
  requirement: !ruby/object:Gem::Requirement
@@ -109,35 +109,7 @@ dependencies:
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
111
  - !ruby/object:Gem::Dependency
112
- name: mysql2
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - ">="
116
- - !ruby/object:Gem::Version
117
- version: '0'
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - ">="
123
- - !ruby/object:Gem::Version
124
- version: '0'
125
- - !ruby/object:Gem::Dependency
126
- name: pg
127
- requirement: !ruby/object:Gem::Requirement
128
- requirements:
129
- - - ">="
130
- - !ruby/object:Gem::Version
131
- version: '0'
132
- type: :development
133
- prerelease: false
134
- version_requirements: !ruby/object:Gem::Requirement
135
- requirements:
136
- - - ">="
137
- - !ruby/object:Gem::Version
138
- version: '0'
139
- - !ruby/object:Gem::Dependency
140
- name: sqlite3
112
+ name: uuidtools
141
113
  requirement: !ruby/object:Gem::Requirement
142
114
  requirements:
143
115
  - - ">="
@@ -151,7 +123,7 @@ dependencies:
151
123
  - !ruby/object:Gem::Version
152
124
  version: '0'
153
125
  - !ruby/object:Gem::Dependency
154
- name: uuidtools
126
+ name: database_cleaner
155
127
  requirement: !ruby/object:Gem::Requirement
156
128
  requirements:
157
129
  - - ">="
@@ -165,7 +137,7 @@ dependencies:
165
137
  - !ruby/object:Gem::Version
166
138
  version: '0'
167
139
  - !ruby/object:Gem::Dependency
168
- name: database_cleaner
140
+ name: appraisal
169
141
  requirement: !ruby/object:Gem::Requirement
170
142
  requirements:
171
143
  - - ">="
@@ -179,7 +151,7 @@ dependencies:
179
151
  - !ruby/object:Gem::Version
180
152
  version: '0'
181
153
  - !ruby/object:Gem::Dependency
182
- name: appraisal
154
+ name: timecop
183
155
  requirement: !ruby/object:Gem::Requirement
184
156
  requirements:
185
157
  - - ">="
@@ -200,6 +172,7 @@ extensions: []
200
172
  extra_rdoc_files: []
201
173
  files:
202
174
  - ".gitignore"
175
+ - ".rspec"
203
176
  - ".travis.yml"
204
177
  - ".yardopts"
205
178
  - Appraisals
@@ -228,21 +201,29 @@ files:
228
201
  - lib/closure_tree/support.rb
229
202
  - lib/closure_tree/support_attributes.rb
230
203
  - lib/closure_tree/support_flags.rb
204
+ - lib/closure_tree/test/matcher.rb
231
205
  - lib/closure_tree/version.rb
232
206
  - mktree.rb
207
+ - spec/cache_invalidation_spec.rb
233
208
  - spec/cuisine_type_spec.rb
234
209
  - spec/db/database.yml
210
+ - spec/db/models.rb
235
211
  - spec/db/schema.rb
236
212
  - spec/fixtures/tags.yml
237
213
  - spec/hierarchy_maintenance_spec.rb
238
214
  - spec/label_spec.rb
215
+ - spec/matcher_spec.rb
239
216
  - spec/metal_spec.rb
240
217
  - spec/model_spec.rb
241
218
  - spec/namespace_type_spec.rb
242
- - spec/parallel_prepend_sibling_spec.rb
243
219
  - spec/parallel_spec.rb
244
220
  - spec/spec_helper.rb
245
- - spec/support/models.rb
221
+ - spec/support/database.rb
222
+ - spec/support/database_cleaner.rb
223
+ - spec/support/deprecated/attr_accessible.rb
224
+ - spec/support/hash_monkey_patch.rb
225
+ - spec/support/helpers.rb
226
+ - spec/support/sqlite3_with_advisory_lock.rb
246
227
  - spec/support_spec.rb
247
228
  - spec/tag_examples.rb
248
229
  - spec/tag_spec.rb
@@ -261,7 +242,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
261
242
  requirements:
262
243
  - - ">="
263
244
  - !ruby/object:Gem::Version
264
- version: '0'
245
+ version: 1.9.3
265
246
  required_rubygems_version: !ruby/object:Gem::Requirement
266
247
  requirements:
267
248
  - - ">="
@@ -274,19 +255,26 @@ signing_key:
274
255
  specification_version: 4
275
256
  summary: Easily and efficiently make your ActiveRecord model support hierarchies
276
257
  test_files:
258
+ - spec/cache_invalidation_spec.rb
277
259
  - spec/cuisine_type_spec.rb
278
260
  - spec/db/database.yml
261
+ - spec/db/models.rb
279
262
  - spec/db/schema.rb
280
263
  - spec/fixtures/tags.yml
281
264
  - spec/hierarchy_maintenance_spec.rb
282
265
  - spec/label_spec.rb
266
+ - spec/matcher_spec.rb
283
267
  - spec/metal_spec.rb
284
268
  - spec/model_spec.rb
285
269
  - spec/namespace_type_spec.rb
286
- - spec/parallel_prepend_sibling_spec.rb
287
270
  - spec/parallel_spec.rb
288
271
  - spec/spec_helper.rb
289
- - spec/support/models.rb
272
+ - spec/support/database.rb
273
+ - spec/support/database_cleaner.rb
274
+ - spec/support/deprecated/attr_accessible.rb
275
+ - spec/support/hash_monkey_patch.rb
276
+ - spec/support/helpers.rb
277
+ - spec/support/sqlite3_with_advisory_lock.rb
290
278
  - spec/support_spec.rb
291
279
  - spec/tag_examples.rb
292
280
  - spec/tag_spec.rb
@@ -1,42 +0,0 @@
1
- require 'spec_helper'
2
- require 'securerandom'
3
-
4
- describe "threadhot", concurrency: true do
5
-
6
- before :each do
7
- @iterations = 5
8
- @workers = 8
9
- end
10
-
11
- def prepend_sibling_at_even_second(run_at)
12
- ActiveRecord::Base.connection.reconnect!
13
- sibling = Label.new(:name => SecureRandom.hex(10))
14
- target = Label.find(@target.id)
15
- sleep(run_at - Time.now.to_f)
16
- target.prepend_sibling sibling
17
- end
18
-
19
- def run_workers
20
- start_time = Time.now.to_i + 2
21
- @times = @iterations.times.collect { |ea| start_time + (ea * 2) }
22
- @names = @times.collect { |ea| ea.to_s }
23
- @threads = @workers.times.collect do
24
- Thread.new do
25
- @times.each { |ea| prepend_sibling_at_even_second(ea) }
26
- end
27
- end
28
- @threads.each { |ea| ea.join }
29
- end
30
-
31
- it "prepend_sibling on a non-root node doesn't cause deadlocks" do
32
- @target = Label.find_or_create_by_path %w(root parent)
33
- run_workers
34
- children = Label.roots
35
- uniq_sort_orders = children.collect { |ea| ea.sort_order }.uniq
36
- children.size.should == uniq_sort_orders.size
37
-
38
- # The only non-root node should be "root":
39
- Label.all.select { |ea| ea.root? }.should == [@target.parent]
40
- end
41
-
42
- end