From b6f6e1cf6efd8d45f5422e8bc18a7a1dd243f36b Mon Sep 17 00:00:00 2001 From: Albert Agram Date: Mon, 2 Jan 2023 20:46:22 -0500 Subject: [PATCH 1/2] minitest migration 5 --- spec/closure_tree/parallel_spec.rb | 159 -------------------------- spec/closure_tree/user_spec.rb | 174 ---------------------------- test/closure_tree/parallel_test.rb | 154 +++++++++++++++++++++++++ test/closure_tree/user_test.rb | 175 +++++++++++++++++++++++++++++ test/test_helper.rb | 6 +- 5 files changed, 333 insertions(+), 335 deletions(-) delete mode 100644 spec/closure_tree/parallel_spec.rb delete mode 100644 spec/closure_tree/user_spec.rb create mode 100644 test/closure_tree/parallel_test.rb create mode 100644 test/closure_tree/user_test.rb diff --git a/spec/closure_tree/parallel_spec.rb b/spec/closure_tree/parallel_spec.rb deleted file mode 100644 index 4ec3a61..0000000 --- a/spec/closure_tree/parallel_spec.rb +++ /dev/null @@ -1,159 +0,0 @@ -require 'spec_helper' - -# We don't need to run the expensive parallel tests for every combination of prefix/suffix. -# Those affect SQL generation, not parallelism. -# SQLite doesn't support concurrency reliably, either. -def run_parallel_tests? - !sqlite? && - ActiveRecord::Base.table_name_prefix.empty? && - ActiveRecord::Base.table_name_suffix.empty? -end - -def max_threads - 5 -end - -class WorkerBase - extend Forwardable - attr_reader :name - def_delegators :@thread, :join, :wakeup, :status, :to_s - - def log(msg) - puts("#{Thread.current}: #{msg}") if ENV['VERBOSE'] - end - - def initialize(target, name) - @target = target - @name = name - @thread = Thread.new do - ActiveRecord::Base.connection_pool.with_connection { before_work } if respond_to? :before_work - log 'going to sleep...' - sleep - log 'woke up...' - ActiveRecord::Base.connection_pool.with_connection { work } - log 'done.' - end - end -end - -class FindOrCreateWorker < WorkerBase - def work - path = [name, :a, :b, :c] - log "making #{path}..." - t = (@target || Tag).find_or_create_by_path(path) - log "made #{t.id}, #{t.ancestry_path}" - end -end - -RSpec.describe 'Concurrent creation' do - before :each do - @target = nil - @iterations = 5 - end - - def log(msg) - puts(msg) if ENV['VERBOSE'] - end - - def run_workers(worker_class = FindOrCreateWorker) - @names = @iterations.times.map { |iter| "iteration ##{iter}" } - @names.each do |name| - workers = max_threads.times.map { worker_class.new(@target, name) } - # Wait for all the threads to get ready: - while true - unready_workers = workers.select { |ea| ea.status != 'sleep' } - if unready_workers.empty? - break - else - log "Not ready to wakeup: #{unready_workers.map { |ea| [ea.to_s, ea.status] }}" - sleep(0.1) - end - end - sleep(0.25) - # OK, GO! - log 'Calling .wakeup on all workers...' - workers.each(&:wakeup) - sleep(0.25) - # Then wait for them to finish: - log 'Calling .join on all workers...' - workers.each(&:join) - end - # Ensure we're still connected: - ActiveRecord::Base.connection_pool.connection - end - - it 'will not create dupes from class methods' do - run_workers - expect(Tag.roots.collect { |ea| ea.name }).to match_array(@names) - # No dupe children: - %w(a b c).each do |ea| - expect(Tag.where(name: ea).size).to eq(@iterations) - end - end - - it 'will not create dupes from instance methods' do - @target = Tag.create!(name: 'root') - run_workers - expect(@target.reload.children.collect { |ea| ea.name }).to match_array(@names) - expect(Tag.where(name: @names).size).to eq(@iterations) - %w(a b c).each do |ea| - expect(Tag.where(name: ea).size).to eq(@iterations) - end - end - - it 'creates dupe roots without advisory locks' do - # disable with_advisory_lock: - allow(Tag).to receive(:with_advisory_lock) { |_lock_name, &block| block.call } - run_workers - # duplication from at least one iteration: - expect(Tag.where(name: @names).size).to be > @iterations - end - - class SiblingPrependerWorker < WorkerBase - def before_work - @target.reload - @sibling = Label.new(name: SecureRandom.hex(10)) - end - - def work - @target.prepend_sibling @sibling - end - end - - it 'fails to deadlock while simultaneously deleting items from the same hierarchy' do - target = User.find_or_create_by_path((1..200).to_a.map { |ea| ea.to_s }) - emails = target.self_and_ancestors.to_a.map(&:email).shuffle - Parallel.map(emails, :in_threads => max_threads) do |email| - ActiveRecord::Base.connection_pool.with_connection do - User.transaction do - log "Destroying #{email}..." - User.where(email: email).destroy_all - end - end - end - User.connection.reconnect! - expect(User.all).to be_empty - end - - class SiblingPrependerWorker < WorkerBase - def before_work - @target.reload - @sibling = Label.new(name: SecureRandom.hex(10)) - end - - def work - @target.prepend_sibling @sibling - end - end - - it 'fails to deadlock from prepending siblings' do - @target = Label.find_or_create_by_path %w(root parent) - run_workers(SiblingPrependerWorker) - children = Label.roots - uniq_order_values = children.collect { |ea| ea.order_value }.uniq - expect(children.size).to eq(uniq_order_values.size) - - # The only non-root node should be "root": - expect(Label.all.select { |ea| ea.root? }).to eq([@target.parent]) - end -end if run_parallel_tests? diff --git a/spec/closure_tree/user_spec.rb b/spec/closure_tree/user_spec.rb deleted file mode 100644 index 327224a..0000000 --- a/spec/closure_tree/user_spec.rb +++ /dev/null @@ -1,174 +0,0 @@ -require 'spec_helper' - -RSpec.describe "empty db" do - - context "empty db" do - it "should return no entities" do - expect(User.roots).to be_empty - expect(User.leaves).to be_empty - end - end - - context "1 user db" do - it "should return the only entity as a root and leaf" do - a = User.create!(:email => "me@domain.com") - expect(User.roots).to eq([a]) - expect(User.leaves).to eq([a]) - end - end - - context "2 user db" do - it "should return a simple root and leaf" do - root = User.create!(:email => "first@t.co") - leaf = root.children.create!(:email => "second@t.co") - expect(User.roots).to eq([root]) - expect(User.leaves).to eq([leaf]) - end - end - - context "3 User collection.create db" do - before :each do - @root = User.create! :email => "poppy@t.co" - @mid = @root.children.create! :email => "matt@t.co" - @leaf = @mid.children.create! :email => "james@t.co" - @root_id = @root.id - end - - it "should create all Users" do - expect(User.all.to_a).to match_array([@root, @mid, @leaf]) - end - - it 'orders self_and_ancestor_ids nearest generation first' do - expect(@leaf.self_and_ancestor_ids).to eq([@leaf.id, @mid.id, @root.id]) - end - - it 'orders self_and_descendant_ids nearest generation first' do - expect(@root.self_and_descendant_ids).to eq([@root.id, @mid.id, @leaf.id]) - end - - it "should have children" do - expect(@root.children.to_a).to eq([@mid]) - expect(@mid.children.to_a).to eq([@leaf]) - expect(@leaf.children.to_a).to eq([]) - end - - it "roots should have children" do - expect(User.roots.first.children.to_a).to match_array([@mid]) - end - - it "should return a root and leaf without middle User" do - expect(User.roots.to_a).to eq([@root]) - expect(User.leaves.to_a).to eq([@leaf]) - end - - it "should delete leaves" do - User.leaves.destroy_all - expect(User.roots.to_a).to eq([@root]) # untouched - expect(User.leaves.to_a).to eq([@mid]) - end - - it "should delete roots and maintain hierarchies" do - User.roots.destroy_all - assert_mid_and_leaf_remain - end - - it "should root all children" do - @root.destroy - assert_mid_and_leaf_remain - end - - def assert_mid_and_leaf_remain - expect(ReferralHierarchy.where(:ancestor_id => @root_id)).to be_empty - expect(ReferralHierarchy.where(:descendant_id => @root_id)).to be_empty - expect(@mid.ancestry_path).to eq(%w{matt@t.co}) - expect(@leaf.ancestry_path).to eq(%w{matt@t.co james@t.co}) - expect(@mid.self_and_descendants.to_a).to match_array([@mid, @leaf]) - expect(User.roots).to eq([@mid]) - expect(User.leaves).to eq([@leaf]) - end - end - - it "supports users with contracts" do - u = User.find_or_create_by_path(%w(a@t.co b@t.co c@t.co)) - expect(u.descendant_ids).to eq([]) - expect(u.ancestor_ids).to eq([u.parent.id, u.root.id]) - expect(u.self_and_ancestor_ids).to eq([u.id, u.parent.id, u.root.id]) - expect(u.root.descendant_ids).to eq([u.parent.id, u.id]) - expect(u.root.ancestor_ids).to eq([]) - expect(u.root.self_and_ancestor_ids).to eq([u.root.id]) - c1 = u.contracts.create! - c2 = u.parent.contracts.create! - expect(u.root.indirect_contracts.to_a).to match_array([c1, c2]) - end - - it "supports << on shallow unsaved hierarchies" do - a = User.new(:email => "a") - b = User.new(:email => "b") - a.children << b - a.save - expect(User.roots).to eq([a]) - expect(User.leaves).to eq([b]) - expect(b.ancestry_path).to eq(%w(a b)) - end - - it "supports << on deep unsaved hierarchies" do - a = User.new(:email => "a") - b1 = User.new(:email => "b1") - a.children << b1 - b2 = User.new(:email => "b2") - a.children << b2 - c1 = User.new(:email => "c1") - b2.children << c1 - c2 = User.new(:email => "c2") - b2.children << c2 - d = User.new(:email => "d") - c2.children << d - - a.save - expect(User.roots.to_a).to eq([a]) - expect(User.leaves.to_a).to match_array([b1, c1, d]) - expect(d.ancestry_path).to eq(%w(a b2 c2 d)) - end - - it "supports siblings" do - expect(User._ct.order_option?).to be_falsey - a = User.create(:email => "a") - b1 = a.children.create(:email => "b1") - b2 = a.children.create(:email => "b2") - b3 = a.children.create(:email => "b3") - expect(a.siblings).to be_empty - expect(b1.siblings.to_a).to match_array([b2, b3]) - end - - context "when a user is not yet saved" do - it "supports siblings" do - expect(User._ct.order_option?).to be_falsey - a = User.create(:email => "a") - b1 = a.children.new(:email => "b1") - b2 = a.children.create(:email => "b2") - b3 = a.children.create(:email => "b3") - expect(a.siblings).to be_empty - expect(b1.siblings.to_a).to match_array([b2, b3]) - end - end - - it "properly nullifies descendents" do - c = User.find_or_create_by_path %w(a b c) - b = c.parent - c.root.destroy - expect(b.reload).to be_root - expect(b.child_ids).to eq([c.id]) - end - - context "roots" do - it "works on models without ordering" do - expected = ("a".."z").to_a - expected.shuffle.each do |ea| - User.create! do |u| - u.email = ea - end - end - expect(User.roots.collect { |ea| ea.email }.sort).to eq(expected) - end - end -end diff --git a/test/closure_tree/parallel_test.rb b/test/closure_tree/parallel_test.rb new file mode 100644 index 0000000..fe7534c --- /dev/null +++ b/test/closure_tree/parallel_test.rb @@ -0,0 +1,154 @@ +# frozen_string_literal: true + +require "test_helper" + +# We don't need to run the expensive parallel tests for every combination of prefix/suffix. +# Those affect SQL generation, not parallelism. +# SQLite doesn't support concurrency reliably, either. +def run_parallel_tests? + !sqlite? && + ActiveRecord::Base.table_name_prefix.empty? && + ActiveRecord::Base.table_name_suffix.empty? +end + +def max_threads + 5 +end + +class WorkerBase + extend Forwardable + attr_reader :name + + def_delegators :@thread, :join, :wakeup, :status, :to_s + + def log(msg) + puts("#{Thread.current}: #{msg}") if ENV["VERBOSE"] + end + + def initialize(target, name) + @target = target + @name = name + @thread = Thread.new do + ActiveRecord::Base.connection_pool.with_connection { before_work } if respond_to? :before_work + log "going to sleep..." + sleep + log "woke up..." + ActiveRecord::Base.connection_pool.with_connection { work } + log "done." + end + end +end + +class FindOrCreateWorker < WorkerBase + def work + path = [name, :a, :b, :c] + log "making #{path}..." + t = (@target || Tag).find_or_create_by_path(path) + log "made #{t.id}, #{t.ancestry_path}" + end +end + +class SiblingPrependerWorker < WorkerBase + def before_work + @target.reload + @sibling = Label.new(name: SecureRandom.hex(10)) + end + + def work + @target.prepend_sibling @sibling + end +end + +if run_parallel_tests? + describe "Concurrent creation" do + before do + @target = nil + @iterations = 5 + end + + def log(msg) + puts(msg) if ENV["VERBOSE"] + end + + def run_workers(worker_class = FindOrCreateWorker) + @names = @iterations.times.map { |iter| "iteration ##{iter}" } + @names.each do |name| + workers = max_threads.times.map { worker_class.new(@target, name) } + # Wait for all the threads to get ready: + while true + unready_workers = workers.select { |ea| ea.status != "sleep" } + if unready_workers.empty? + break + else + log "Not ready to wakeup: #{unready_workers.map { |ea| [ea.to_s, ea.status] }}" + sleep(0.1) + end + end + sleep(0.25) + # OK, GO! + log "Calling .wakeup on all workers..." + workers.each(&:wakeup) + sleep(0.25) + # Then wait for them to finish: + log "Calling .join on all workers..." + workers.each(&:join) + end + # Ensure we're still connected: + ActiveRecord::Base.connection_pool.connection + end + + it "will not create dupes from class methods" do + run_workers + assert_equal @names.sort, Tag.roots.collect { |ea| ea.name }.sort + # No dupe children: + %w[a b c].each do |ea| + assert_equal @iterations, Tag.where(name: ea).size + end + end + + it "will not create dupes from instance methods" do + @target = Tag.create!(name: "root") + run_workers + assert_equal @names.sort, @target.reload.children.collect { |ea| ea.name }.sort + assert_equal @iterations, Tag.where(name: @names).size + %w[a b c].each do |ea| + assert_equal @iterations, Tag.where(name: ea).size + end + end + + it "creates dupe roots without advisory locks" do + # disable with_advisory_lock: + Tag.stub(:with_advisory_lock, ->(_lock_name, &block) { block.call }) do + run_workers + # duplication from at least one iteration: + assert Tag.where(name: @names).size > @iterations + end + end + + it "fails to deadlock while simultaneously deleting items from the same hierarchy" do + target = User.find_or_create_by_path((1..200).to_a.map { |ea| ea.to_s }) + emails = target.self_and_ancestors.to_a.map(&:email).shuffle + Parallel.map(emails, in_threads: max_threads) do |email| + ActiveRecord::Base.connection_pool.with_connection do + User.transaction do + log "Destroying #{email}..." + User.where(email: email).destroy_all + end + end + end + User.connection.reconnect! + assert User.all.empty? + end + + it "fails to deadlock from prepending siblings" do + @target = Label.find_or_create_by_path %w[root parent] + run_workers(SiblingPrependerWorker) + children = Label.roots + uniq_order_values = children.collect { |ea| ea.order_value }.uniq + assert_equal uniq_order_values.size, children.size + + # The only non-root node should be "root": + assert_equal([@target.parent], Label.all.select { |ea| ea.root? }) + end + end +end diff --git a/test/closure_tree/user_test.rb b/test/closure_tree/user_test.rb new file mode 100644 index 0000000..5522cf1 --- /dev/null +++ b/test/closure_tree/user_test.rb @@ -0,0 +1,175 @@ +# frozen_string_literal: true + +require "test_helper" + +describe "empty db" do + describe "empty db" do + it "should return no entities" do + assert User.roots.empty? + assert User.leaves.empty? + end + end + + describe "1 user db" do + it "should return the only entity as a root and leaf" do + a = User.create!(email: "me@domain.com") + assert_equal [a], User.roots + assert_equal [a], User.leaves + end + end + + describe "2 user db" do + it "should return a simple root and leaf" do + root = User.create!(email: "first@t.co") + leaf = root.children.create!(email: "second@t.co") + assert_equal [root], User.roots + assert_equal [leaf], User.leaves + end + end + + describe "3 User collection.create db" do + before do + @root = User.create! email: "poppy@t.co" + @mid = @root.children.create! email: "matt@t.co" + @leaf = @mid.children.create! email: "james@t.co" + @root_id = @root.id + end + + it "should create all Users" do + assert_equal [@root, @mid, @leaf], User.all.to_a.sort + end + + it "orders self_and_ancestor_ids nearest generation first" do + assert_equal [@leaf.id, @mid.id, @root.id], @leaf.self_and_ancestor_ids + end + + it "orders self_and_descendant_ids nearest generation first" do + assert_equal [@root.id, @mid.id, @leaf.id], @root.self_and_descendant_ids + end + + it "should have children" do + assert_equal [@mid], @root.children.to_a + assert_equal [@leaf], @mid.children.to_a + assert_equal [], @leaf.children.to_a + end + + it "roots should have children" do + assert_equal [@mid], User.roots.first.children.to_a + end + + it "should return a root and leaf without middle User" do + assert_equal [@root], User.roots.to_a + assert_equal [@leaf], User.leaves.to_a + end + + it "should delete leaves" do + User.leaves.destroy_all + assert_equal [@root], User.roots.to_a # untouched + assert_equal [@mid], User.leaves.to_a + end + + it "should delete roots and maintain hierarchies" do + User.roots.destroy_all + assert_mid_and_leaf_remain + end + + it "should root all children" do + @root.destroy + assert_mid_and_leaf_remain + end + + def assert_mid_and_leaf_remain + assert ReferralHierarchy.where(ancestor_id: @root_id).empty? + assert ReferralHierarchy.where(descendant_id: @root_id).empty? + assert_equal %w[matt@t.co], @mid.ancestry_path + assert_equal %w[matt@t.co james@t.co], @leaf.ancestry_path + assert_equal [@mid, @leaf].sort, @mid.self_and_descendants.to_a.sort + assert_equal [@mid], User.roots + assert_equal [@leaf], User.leaves + end + end + + it "supports users with contracts" do + u = User.find_or_create_by_path(%w[a@t.co b@t.co c@t.co]) + assert_equal [], u.descendant_ids + assert_equal [u.parent.id, u.root.id], u.ancestor_ids + assert_equal [u.id, u.parent.id, u.root.id], u.self_and_ancestor_ids + assert_equal [u.parent.id, u.id], u.root.descendant_ids + assert_equal [], u.root.ancestor_ids + assert_equal [u.root.id], u.root.self_and_ancestor_ids + c1 = u.contracts.create! + c2 = u.parent.contracts.create! + assert_equal [c1, c2].sort, u.root.indirect_contracts.to_a.sort + end + + it "supports << on shallow unsaved hierarchies" do + a = User.new(email: "a") + b = User.new(email: "b") + a.children << b + a.save + assert_equal [a], User.roots + assert_equal [b], User.leaves + assert_equal %w[a b], b.ancestry_path + end + + it "supports << on deep unsaved hierarchies" do + a = User.new(email: "a") + b1 = User.new(email: "b1") + a.children << b1 + b2 = User.new(email: "b2") + a.children << b2 + c1 = User.new(email: "c1") + b2.children << c1 + c2 = User.new(email: "c2") + b2.children << c2 + d = User.new(email: "d") + c2.children << d + + a.save + assert_equal [a], User.roots.to_a + assert_equal [b1, c1, d].sort, User.leaves.to_a.sort + assert_equal %w[a b2 c2 d], d.ancestry_path + end + + it "supports siblings" do + refute User._ct.order_option? + a = User.create(email: "a") + b1 = a.children.create(email: "b1") + b2 = a.children.create(email: "b2") + b3 = a.children.create(email: "b3") + assert a.siblings.empty? + assert_equal [b2, b3].sort, b1.siblings.to_a.sort + end + + describe "when a user is not yet saved" do + it "supports siblings" do + refute User._ct.order_option? + a = User.create(email: "a") + b1 = a.children.new(email: "b1") + b2 = a.children.create(email: "b2") + b3 = a.children.create(email: "b3") + assert a.siblings.empty? + assert_equal [b2, b3].sort, b1.siblings.to_a.sort + end + end + + it "properly nullifies descendents" do + c = User.find_or_create_by_path %w[a b c] + b = c.parent + c.root.destroy + assert b.reload.root? + assert_equal [c.id], b.child_ids + end + + describe "roots" do + it "works on models without ordering" do + expected = ("a".."z").to_a + expected.shuffle.each do |ea| + User.create! do |u| + u.email = ea + end + end + assert_equal(expected, User.roots.collect { |ea| ea.email }.sort) + end + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 969e376..94fcfaa 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -9,6 +9,7 @@ require 'minitest/autorun' require 'database_cleaner' require 'support/query_counter' +require 'parallel' ActiveRecord::Base.configurations = { default_env: { @@ -39,8 +40,6 @@ def sqlite? env_db == :sqlite3 end -ENV['WITH_ADVISORY_LOCK_PREFIX'] ||= SecureRandom.hex - ActiveRecord::Base.connection.recreate_database('closure_tree_test') unless sqlite? puts "Testing with #{env_db} database, ActiveRecord #{ActiveRecord.gem_version} and #{RUBY_ENGINE} #{RUBY_ENGINE_VERSION} as #{RUBY_VERSION}" @@ -62,6 +61,9 @@ class Spec end end +# Configure parallel tests +Thread.abort_on_exception = true + require 'closure_tree' require_relative '../spec/support/schema' require_relative '../spec/support/models' From 5a470235837bec25b09bb725ba791e2b35ab6ef2 Mon Sep 17 00:00:00 2001 From: Albert Agram Date: Tue, 3 Jan 2023 11:54:16 -0500 Subject: [PATCH 2/2] use skip --- test/closure_tree/parallel_test.rb | 156 +++++++++++++++-------------- 1 file changed, 82 insertions(+), 74 deletions(-) diff --git a/test/closure_tree/parallel_test.rb b/test/closure_tree/parallel_test.rb index fe7534c..3d6202b 100644 --- a/test/closure_tree/parallel_test.rb +++ b/test/closure_tree/parallel_test.rb @@ -59,96 +59,104 @@ def work end end -if run_parallel_tests? - describe "Concurrent creation" do - before do - @target = nil - @iterations = 5 - end +describe "Concurrent creation" do + before do + @target = nil + @iterations = 5 + end - def log(msg) - puts(msg) if ENV["VERBOSE"] - end + def log(msg) + puts(msg) if ENV["VERBOSE"] + end - def run_workers(worker_class = FindOrCreateWorker) - @names = @iterations.times.map { |iter| "iteration ##{iter}" } - @names.each do |name| - workers = max_threads.times.map { worker_class.new(@target, name) } - # Wait for all the threads to get ready: - while true - unready_workers = workers.select { |ea| ea.status != "sleep" } - if unready_workers.empty? - break - else - log "Not ready to wakeup: #{unready_workers.map { |ea| [ea.to_s, ea.status] }}" - sleep(0.1) - end + def run_workers(worker_class = FindOrCreateWorker) + @names = @iterations.times.map { |iter| "iteration ##{iter}" } + @names.each do |name| + workers = max_threads.times.map { worker_class.new(@target, name) } + # Wait for all the threads to get ready: + while true + unready_workers = workers.select { |ea| ea.status != "sleep" } + if unready_workers.empty? + break + else + log "Not ready to wakeup: #{unready_workers.map { |ea| [ea.to_s, ea.status] }}" + sleep(0.1) end - sleep(0.25) - # OK, GO! - log "Calling .wakeup on all workers..." - workers.each(&:wakeup) - sleep(0.25) - # Then wait for them to finish: - log "Calling .join on all workers..." - workers.each(&:join) end - # Ensure we're still connected: - ActiveRecord::Base.connection_pool.connection + sleep(0.25) + # OK, GO! + log "Calling .wakeup on all workers..." + workers.each(&:wakeup) + sleep(0.25) + # Then wait for them to finish: + log "Calling .join on all workers..." + workers.each(&:join) end + # Ensure we're still connected: + ActiveRecord::Base.connection_pool.connection + end - it "will not create dupes from class methods" do - run_workers - assert_equal @names.sort, Tag.roots.collect { |ea| ea.name }.sort - # No dupe children: - %w[a b c].each do |ea| - assert_equal @iterations, Tag.where(name: ea).size - end + it "will not create dupes from class methods" do + skip("unsupported") unless run_parallel_tests? + + run_workers + assert_equal @names.sort, Tag.roots.collect { |ea| ea.name }.sort + # No dupe children: + %w[a b c].each do |ea| + assert_equal @iterations, Tag.where(name: ea).size end + end - it "will not create dupes from instance methods" do - @target = Tag.create!(name: "root") - run_workers - assert_equal @names.sort, @target.reload.children.collect { |ea| ea.name }.sort - assert_equal @iterations, Tag.where(name: @names).size - %w[a b c].each do |ea| - assert_equal @iterations, Tag.where(name: ea).size - end + it "will not create dupes from instance methods" do + skip("unsupported") unless run_parallel_tests? + + @target = Tag.create!(name: "root") + run_workers + assert_equal @names.sort, @target.reload.children.collect { |ea| ea.name }.sort + assert_equal @iterations, Tag.where(name: @names).size + %w[a b c].each do |ea| + assert_equal @iterations, Tag.where(name: ea).size end + end - it "creates dupe roots without advisory locks" do - # disable with_advisory_lock: - Tag.stub(:with_advisory_lock, ->(_lock_name, &block) { block.call }) do - run_workers - # duplication from at least one iteration: - assert Tag.where(name: @names).size > @iterations - end + it "creates dupe roots without advisory locks" do + skip("unsupported") unless run_parallel_tests? + + # disable with_advisory_lock: + Tag.stub(:with_advisory_lock, ->(_lock_name, &block) { block.call }) do + run_workers + # duplication from at least one iteration: + assert Tag.where(name: @names).size > @iterations end + end + + it "fails to deadlock while simultaneously deleting items from the same hierarchy" do + skip("unsupported") unless run_parallel_tests? - it "fails to deadlock while simultaneously deleting items from the same hierarchy" do - target = User.find_or_create_by_path((1..200).to_a.map { |ea| ea.to_s }) - emails = target.self_and_ancestors.to_a.map(&:email).shuffle - Parallel.map(emails, in_threads: max_threads) do |email| - ActiveRecord::Base.connection_pool.with_connection do - User.transaction do - log "Destroying #{email}..." - User.where(email: email).destroy_all - end + target = User.find_or_create_by_path((1..200).to_a.map { |ea| ea.to_s }) + emails = target.self_and_ancestors.to_a.map(&:email).shuffle + Parallel.map(emails, in_threads: max_threads) do |email| + ActiveRecord::Base.connection_pool.with_connection do + User.transaction do + log "Destroying #{email}..." + User.where(email: email).destroy_all end end - User.connection.reconnect! - assert User.all.empty? end + User.connection.reconnect! + assert User.all.empty? + end - it "fails to deadlock from prepending siblings" do - @target = Label.find_or_create_by_path %w[root parent] - run_workers(SiblingPrependerWorker) - children = Label.roots - uniq_order_values = children.collect { |ea| ea.order_value }.uniq - assert_equal uniq_order_values.size, children.size + it "fails to deadlock from prepending siblings" do + skip("unsupported") unless run_parallel_tests? - # The only non-root node should be "root": - assert_equal([@target.parent], Label.all.select { |ea| ea.root? }) - end + @target = Label.find_or_create_by_path %w[root parent] + run_workers(SiblingPrependerWorker) + children = Label.roots + uniq_order_values = children.collect { |ea| ea.order_value }.uniq + assert_equal uniq_order_values.size, children.size + + # The only non-root node should be "root": + assert_equal([@target.parent], Label.all.select { |ea| ea.root? }) end end