closure_tree 4.5.0 → 4.6.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.
- 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
|