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.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/.travis.yml +11 -3
- data/CHANGELOG.md +13 -0
- data/Gemfile +13 -0
- data/README.md +61 -12
- data/closure_tree.gemspec +4 -5
- data/gemfiles/activerecord_3.2.gemfile +12 -0
- data/gemfiles/activerecord_4.0.gemfile +12 -0
- data/gemfiles/activerecord_4.1.gemfile +12 -0
- data/gemfiles/activerecord_edge.gemfile +12 -0
- data/lib/closure_tree/acts_as_tree.rb +2 -9
- data/lib/closure_tree/deterministic_ordering.rb +4 -0
- data/lib/closure_tree/finders.rb +4 -4
- data/lib/closure_tree/hash_tree.rb +1 -1
- data/lib/closure_tree/hierarchy_maintenance.rb +19 -6
- data/lib/closure_tree/model.rb +2 -10
- data/lib/closure_tree/numeric_deterministic_ordering.rb +53 -36
- data/lib/closure_tree/numeric_order_support.rb +20 -13
- data/lib/closure_tree/support.rb +8 -0
- data/lib/closure_tree/support_attributes.rb +10 -0
- data/lib/closure_tree/test/matcher.rb +85 -0
- data/lib/closure_tree/version.rb +1 -1
- data/lib/closure_tree.rb +14 -1
- data/spec/cache_invalidation_spec.rb +39 -0
- data/spec/{support → db}/models.rb +6 -0
- data/spec/db/schema.rb +18 -0
- data/spec/label_spec.rb +171 -46
- data/spec/matcher_spec.rb +32 -0
- data/spec/parallel_spec.rb +85 -49
- data/spec/spec_helper.rb +6 -96
- data/spec/support/database.rb +49 -0
- data/spec/support/database_cleaner.rb +14 -0
- data/spec/support/deprecated/attr_accessible.rb +5 -0
- data/spec/support/hash_monkey_patch.rb +13 -0
- data/spec/support/helpers.rb +8 -0
- data/spec/support/sqlite3_with_advisory_lock.rb +10 -0
- data/tests.sh +7 -2
- metadata +31 -43
- data/spec/parallel_prepend_sibling_spec.rb +0 -42
data/spec/parallel_spec.rb
CHANGED
@@ -1,85 +1,99 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'securerandom'
|
2
3
|
|
3
|
-
|
4
|
-
|
5
|
-
|
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
|
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
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
29
|
-
DatabaseCleaner.clean
|
30
|
-
end
|
34
|
+
describe 'Concurrent creation', if: support_concurrency do
|
31
35
|
|
32
|
-
|
33
|
-
@
|
34
|
-
|
35
|
-
|
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
|
-
|
45
|
-
@iterations.times.
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
52
|
-
|
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
|
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(:
|
64
|
+
Tag.where(name: ea).size.should == @iterations
|
62
65
|
end
|
63
66
|
end
|
64
67
|
|
65
|
-
it
|
66
|
-
@
|
68
|
+
it 'will not create dupes from instance methods' do
|
69
|
+
@target = Tag.create!(name: 'root')
|
67
70
|
run_workers
|
68
|
-
@
|
69
|
-
Tag.where(:
|
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(:
|
74
|
+
Tag.where(name: ea).size.should == @iterations
|
72
75
|
end
|
73
76
|
end
|
74
77
|
|
75
|
-
it
|
78
|
+
it 'creates dupe roots without advisory locks' do
|
76
79
|
# disable with_advisory_lock:
|
77
|
-
Tag.stub(:with_advisory_lock).and_return { |
|
80
|
+
Tag.stub(:with_advisory_lock).and_return { |_lock_name, &block| block.call }
|
78
81
|
run_workers
|
79
|
-
Tag.where(:
|
82
|
+
Tag.where(name: @names).size.should > @iterations
|
80
83
|
end
|
81
84
|
|
82
|
-
|
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(:
|
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
|
-
|
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(:
|
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
|
-
|
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.
|
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
|
-
|
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.
|
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
|
data/tests.sh
CHANGED
@@ -2,7 +2,12 @@
|
|
2
2
|
|
3
3
|
appraisal install
|
4
4
|
|
5
|
-
for
|
5
|
+
for RMI in 1.9.3-p429 2.1.2
|
6
6
|
do
|
7
|
-
|
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.
|
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-
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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/
|
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:
|
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/
|
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
|