test-prof 0.11.3 → 1.0.0.rc2
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/CHANGELOG.md +122 -447
- data/LICENSE.txt +1 -1
- data/README.md +9 -13
- data/config/default.yml +0 -15
- data/config/rubocop-rspec.yml +6 -0
- data/lib/minitest/test_prof_plugin.rb +3 -0
- data/lib/test_prof/any_fixture.rb +116 -7
- data/lib/test_prof/any_fixture/dump.rb +207 -0
- data/lib/test_prof/any_fixture/dump/base_adapter.rb +43 -0
- data/lib/test_prof/any_fixture/dump/digest.rb +29 -0
- data/lib/test_prof/any_fixture/dump/postgresql.rb +91 -0
- data/lib/test_prof/any_fixture/dump/sqlite.rb +42 -0
- data/lib/test_prof/before_all.rb +9 -4
- data/lib/test_prof/before_all/adapters/active_record.rb +14 -5
- data/lib/test_prof/cops/rspec/aggregate_examples.rb +2 -2
- data/lib/test_prof/cops/rspec/aggregate_examples/its.rb +1 -1
- data/lib/test_prof/cops/rspec/aggregate_examples/line_range_helpers.rb +1 -1
- data/lib/test_prof/cops/rspec/aggregate_examples/matchers_with_side_effects.rb +1 -1
- data/lib/test_prof/cops/rspec/aggregate_examples/metadata_helpers.rb +1 -1
- data/lib/test_prof/cops/rspec/aggregate_examples/node_matchers.rb +1 -1
- data/lib/test_prof/event_prof/instrumentations/active_support.rb +22 -4
- data/lib/test_prof/recipes/minitest/before_all.rb +48 -23
- data/lib/test_prof/recipes/minitest/sample.rb +6 -10
- data/lib/test_prof/recipes/rspec/before_all.rb +10 -10
- data/lib/test_prof/recipes/rspec/let_it_be.rb +111 -13
- data/lib/test_prof/recipes/rspec/sample.rb +4 -2
- data/lib/test_prof/rubocop.rb +0 -1
- data/lib/test_prof/stack_prof.rb +3 -0
- data/lib/test_prof/version.rb +1 -1
- metadata +23 -21
- data/lib/test_prof/cops/rspec/aggregate_failures.rb +0 -26
- data/lib/test_prof/ext/active_record_3.rb +0 -27
- data/lib/test_prof/recipes/active_record_one_love.rb +0 -6
- data/lib/test_prof/recipes/active_record_shared_connection.rb +0 -77
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TestProf
|
4
|
+
module AnyFixture
|
5
|
+
class Dump
|
6
|
+
class BaseAdapter
|
7
|
+
def reset_sequence!(_table_name, _start)
|
8
|
+
end
|
9
|
+
|
10
|
+
def compile_sql(sql, _binds)
|
11
|
+
sql
|
12
|
+
end
|
13
|
+
|
14
|
+
def setup_env
|
15
|
+
end
|
16
|
+
|
17
|
+
def teardown_env
|
18
|
+
end
|
19
|
+
|
20
|
+
def import(_path)
|
21
|
+
false
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def while_disconnected
|
27
|
+
conn.disconnect!
|
28
|
+
yield
|
29
|
+
ensure
|
30
|
+
conn.reconnect!
|
31
|
+
end
|
32
|
+
|
33
|
+
def conn
|
34
|
+
ActiveRecord::Base.connection
|
35
|
+
end
|
36
|
+
|
37
|
+
def execute(query)
|
38
|
+
conn.execute(query)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "digest/sha1"
|
4
|
+
|
5
|
+
module TestProf
|
6
|
+
module AnyFixture
|
7
|
+
class Dump
|
8
|
+
module Digest
|
9
|
+
module_function
|
10
|
+
|
11
|
+
def call(*paths)
|
12
|
+
files = (AnyFixture.config.default_dump_watch_paths + paths).each_with_object([]) do |path_or_glob, acc|
|
13
|
+
if File.file?(path_or_glob)
|
14
|
+
acc << path_or_glob
|
15
|
+
else
|
16
|
+
acc = acc.concat Dir[path_or_glob]
|
17
|
+
end
|
18
|
+
acc
|
19
|
+
end
|
20
|
+
|
21
|
+
return if files.empty?
|
22
|
+
|
23
|
+
file_ids = files.sort.map { |f| "#{File.basename(f)}/#{::Digest::SHA1.file(f).hexdigest}" }
|
24
|
+
::Digest::SHA1.hexdigest(file_ids.join("/"))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "test_prof/any_fixture/dump/base_adapter"
|
4
|
+
|
5
|
+
module TestProf
|
6
|
+
module AnyFixture
|
7
|
+
class Dump
|
8
|
+
class PostgreSQL < BaseAdapter
|
9
|
+
UUID_FUNCTIONS = %w[
|
10
|
+
gen_random_uuid
|
11
|
+
uuid_generate_v4
|
12
|
+
]
|
13
|
+
|
14
|
+
def reset_sequence!(table_name, start)
|
15
|
+
_pk, sequence = conn.pk_and_sequence_for(table_name)
|
16
|
+
return unless sequence
|
17
|
+
|
18
|
+
sequence_name = "#{sequence.schema}.#{sequence.identifier}"
|
19
|
+
|
20
|
+
execute <<~SQL
|
21
|
+
ALTER SEQUENCE #{sequence_name} RESTART WITH #{start}; -- any_fixture:dump
|
22
|
+
SQL
|
23
|
+
end
|
24
|
+
|
25
|
+
def compile_sql(sql, binds)
|
26
|
+
sql.gsub(/\$\d+/) { binds.shift }
|
27
|
+
end
|
28
|
+
|
29
|
+
def import(path)
|
30
|
+
# Test if psql is installed
|
31
|
+
`psql --version`
|
32
|
+
|
33
|
+
tasks = ActiveRecord::Tasks::PostgreSQLDatabaseTasks.new(conn.pool.spec.config.with_indifferent_access)
|
34
|
+
|
35
|
+
while_disconnected do
|
36
|
+
tasks.structure_load(path, "--output=/dev/null")
|
37
|
+
end
|
38
|
+
|
39
|
+
true
|
40
|
+
rescue Errno::ENOENT
|
41
|
+
false
|
42
|
+
end
|
43
|
+
|
44
|
+
def setup_env
|
45
|
+
# Mock UUID generating functions to provide consistent results
|
46
|
+
quoted_functions = UUID_FUNCTIONS.map { |func| "'#{func}'" }.join(", ")
|
47
|
+
|
48
|
+
@uuid_funcs = execute <<~SQL
|
49
|
+
SELECT
|
50
|
+
pp.proname, pn.nspname,
|
51
|
+
pg_get_functiondef(pp.oid) AS definition
|
52
|
+
FROM pg_proc pp
|
53
|
+
JOIN pg_namespace pn
|
54
|
+
ON pn.oid = pp.pronamespace
|
55
|
+
WHERE pp.proname in (#{quoted_functions})
|
56
|
+
ORDER BY pp.oid;
|
57
|
+
SQL
|
58
|
+
|
59
|
+
uuid_funcs.each do |(func, ns, _)|
|
60
|
+
execute <<~SQL
|
61
|
+
CREATE OR REPLACE FUNCTION #{ns}.#{func}()
|
62
|
+
RETURNS UUID
|
63
|
+
LANGUAGE SQL
|
64
|
+
AS $$
|
65
|
+
SELECT md5(random()::TEXT)::UUID;
|
66
|
+
$$; -- any_fixture:dump
|
67
|
+
SQL
|
68
|
+
end
|
69
|
+
|
70
|
+
execute <<~SQL
|
71
|
+
SELECT setseed(#{rand}); -- any_fixture:dump
|
72
|
+
SQL
|
73
|
+
end
|
74
|
+
|
75
|
+
def teardown_env
|
76
|
+
uuid_funcs.each do |(func, ns, definition)|
|
77
|
+
execute "#{definition}; -- any_fixture:dump"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
attr_reader :uuid_funcs
|
84
|
+
|
85
|
+
def execute(query)
|
86
|
+
super.values
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "test_prof/any_fixture/dump/base_adapter"
|
4
|
+
|
5
|
+
module TestProf
|
6
|
+
module AnyFixture
|
7
|
+
class Dump
|
8
|
+
class SQLite < BaseAdapter
|
9
|
+
def reset_sequence!(table_name, start)
|
10
|
+
execute <<~SQL.chomp
|
11
|
+
DELETE FROM sqlite_sequence WHERE name=#{table_name}
|
12
|
+
SQL
|
13
|
+
|
14
|
+
execute <<~SQL.chomp
|
15
|
+
INSERT INTO sqlite_sequence (name, seq)
|
16
|
+
VALUES (#{table_name}, #{start})
|
17
|
+
SQL
|
18
|
+
end
|
19
|
+
|
20
|
+
def compile_sql(sql, binds)
|
21
|
+
sql.gsub(/\?/) { binds.shift }
|
22
|
+
end
|
23
|
+
|
24
|
+
def import(path)
|
25
|
+
db = conn.pool.spec.config[:database]
|
26
|
+
return false if %r{:memory:}.match?(db)
|
27
|
+
|
28
|
+
# Check that sqlite3 is installed
|
29
|
+
`sqlite3 --version`
|
30
|
+
|
31
|
+
while_disconnected do
|
32
|
+
`sqlite3 #{db} < "#{path}"`
|
33
|
+
end
|
34
|
+
|
35
|
+
true
|
36
|
+
rescue Errno::ENOENT
|
37
|
+
false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/test_prof/before_all.rb
CHANGED
@@ -24,10 +24,6 @@ module TestProf
|
|
24
24
|
yield
|
25
25
|
end
|
26
26
|
|
27
|
-
def within_transaction
|
28
|
-
yield
|
29
|
-
end
|
30
|
-
|
31
27
|
def rollback_transaction
|
32
28
|
raise AdapterMissing if adapter.nil?
|
33
29
|
|
@@ -36,6 +32,12 @@ module TestProf
|
|
36
32
|
end
|
37
33
|
end
|
38
34
|
|
35
|
+
def setup_fixtures(test_object)
|
36
|
+
raise ArgumentError, "Current adapter doesn't support #setup_fixtures" unless adapter.respond_to?(:setup_fixtures)
|
37
|
+
|
38
|
+
adapter.setup_fixtures(test_object)
|
39
|
+
end
|
40
|
+
|
39
41
|
def config
|
40
42
|
@config ||= Configuration.new
|
41
43
|
end
|
@@ -64,8 +66,11 @@ module TestProf
|
|
64
66
|
class Configuration
|
65
67
|
HOOKS = %i[begin rollback].freeze
|
66
68
|
|
69
|
+
attr_accessor :setup_fixtures
|
70
|
+
|
67
71
|
def initialize
|
68
72
|
@hooks = Hash.new { |h, k| h[k] = HooksChain.new(k) }
|
73
|
+
@setup_fixtures = false
|
69
74
|
end
|
70
75
|
|
71
76
|
# Add `before` hook for `begin` or
|
@@ -1,10 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
if ::ActiveRecord::VERSION::MAJOR < 4
|
4
|
-
require "test_prof/ext/active_record_3"
|
5
|
-
using TestProf::ActiveRecord3Transactions
|
6
|
-
end
|
7
|
-
|
8
3
|
module TestProf
|
9
4
|
module BeforeAll
|
10
5
|
module Adapters
|
@@ -23,6 +18,20 @@ module TestProf
|
|
23
18
|
end
|
24
19
|
::ActiveRecord::Base.connection.rollback_transaction
|
25
20
|
end
|
21
|
+
|
22
|
+
def setup_fixtures(test_object)
|
23
|
+
test_object.instance_eval do
|
24
|
+
@@already_loaded_fixtures ||= {}
|
25
|
+
@fixture_cache ||= {}
|
26
|
+
|
27
|
+
if @@already_loaded_fixtures[self.class]
|
28
|
+
@loaded_fixtures = @@already_loaded_fixtures[self.class]
|
29
|
+
else
|
30
|
+
@loaded_fixtures = load_fixtures(config)
|
31
|
+
@@already_loaded_fixtures[self.class] = @loaded_fixtures
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
26
35
|
end
|
27
36
|
end
|
28
37
|
end
|
@@ -12,7 +12,7 @@ module RuboCop
|
|
12
12
|
module RSpec
|
13
13
|
# Checks if example groups contain two or more aggregatable examples.
|
14
14
|
#
|
15
|
-
# @see https://github.com/rubocop-hq/rspec-style-guide#
|
15
|
+
# @see https://github.com/rubocop-hq/rspec-style-guide#expectation-per-example
|
16
16
|
#
|
17
17
|
# This cop is primarily for reducing the cost of repeated expensive
|
18
18
|
# context initialization.
|
@@ -108,7 +108,7 @@ module RuboCop
|
|
108
108
|
# expect(number).to be_odd
|
109
109
|
# end
|
110
110
|
#
|
111
|
-
class AggregateExamples < Cop
|
111
|
+
class AggregateExamples < ::RuboCop::Cop::Cop
|
112
112
|
include LineRangeHelpers
|
113
113
|
include MetadataHelpers
|
114
114
|
include NodeMatchers
|
@@ -5,7 +5,7 @@ require_relative "../language"
|
|
5
5
|
module RuboCop
|
6
6
|
module Cop
|
7
7
|
module RSpec
|
8
|
-
class AggregateExamples < Cop
|
8
|
+
class AggregateExamples < ::RuboCop::Cop::Cop
|
9
9
|
# When aggregated, the expectations will fail when not supposed to or
|
10
10
|
# have a risk of not failing when expected to. One example is
|
11
11
|
# `validate_presence_of :comment` as it leaves an empty comment after
|
@@ -4,13 +4,31 @@ module TestProf::EventProf
|
|
4
4
|
module Instrumentations
|
5
5
|
# Wrapper over ActiveSupport::Notifications
|
6
6
|
module ActiveSupport
|
7
|
+
class Subscriber
|
8
|
+
attr_reader :block, :started_at
|
9
|
+
|
10
|
+
def initialize(block)
|
11
|
+
@block = block
|
12
|
+
end
|
13
|
+
|
14
|
+
def start(*)
|
15
|
+
@started_at = TestProf.now
|
16
|
+
end
|
17
|
+
|
18
|
+
def publish(_name, started_at, finished_at, *)
|
19
|
+
block.call(finished_at - started_at)
|
20
|
+
end
|
21
|
+
|
22
|
+
def finish(*)
|
23
|
+
block.call(TestProf.now - started_at)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
7
27
|
class << self
|
8
|
-
def subscribe(event)
|
28
|
+
def subscribe(event, &block)
|
9
29
|
raise ArgumentError, "Block is required!" unless block_given?
|
10
30
|
|
11
|
-
::ActiveSupport::Notifications.subscribe(event
|
12
|
-
yield (finish - start)
|
13
|
-
end
|
31
|
+
::ActiveSupport::Notifications.subscribe(event, Subscriber.new(block))
|
14
32
|
end
|
15
33
|
|
16
34
|
def instrument(event)
|
@@ -8,41 +8,59 @@ module TestProf
|
|
8
8
|
# store instance variables
|
9
9
|
module Minitest # :nodoc: all
|
10
10
|
class Executor
|
11
|
-
attr_reader :active
|
11
|
+
attr_reader :active, :block, :captured_ivars, :teardown_block, :current_test_object,
|
12
|
+
:setup_fixtures
|
12
13
|
|
13
14
|
alias active? active
|
15
|
+
alias setup_fixtures? setup_fixtures
|
14
16
|
|
15
|
-
def initialize(&block)
|
17
|
+
def initialize(setup_fixtures: false, &block)
|
18
|
+
@setup_fixtures = setup_fixtures
|
16
19
|
@block = block
|
20
|
+
@captured_ivars = []
|
17
21
|
end
|
18
22
|
|
19
|
-
def
|
20
|
-
|
23
|
+
def teardown(&block)
|
24
|
+
@teardown_block = block
|
25
|
+
end
|
26
|
+
|
27
|
+
def activate!(test_object)
|
28
|
+
@current_test_object = test_object
|
29
|
+
|
30
|
+
return restore_ivars(test_object) if active?
|
21
31
|
@active = true
|
22
|
-
|
32
|
+
BeforeAll.setup_fixtures(test_object) if setup_fixtures?
|
23
33
|
BeforeAll.begin_transaction do
|
24
|
-
capture!
|
34
|
+
capture!(test_object)
|
25
35
|
end
|
26
36
|
end
|
27
37
|
|
28
|
-
def
|
29
|
-
@examples_left -= 1
|
30
|
-
return unless @examples_left.zero?
|
31
|
-
|
38
|
+
def deactivate!
|
32
39
|
@active = false
|
40
|
+
|
41
|
+
current_test_object&.instance_eval(&teardown_block) if teardown_block
|
42
|
+
|
43
|
+
@current_test_object = nil
|
33
44
|
BeforeAll.rollback_transaction
|
34
45
|
end
|
35
46
|
|
36
|
-
def capture!
|
37
|
-
|
47
|
+
def capture!(test_object)
|
48
|
+
return unless block
|
49
|
+
|
50
|
+
before_ivars = test_object.instance_variables
|
51
|
+
|
52
|
+
test_object.instance_eval(&block)
|
53
|
+
|
54
|
+
(test_object.instance_variables - before_ivars).each do |ivar|
|
55
|
+
captured_ivars << [ivar, test_object.instance_variable_get(ivar)]
|
56
|
+
end
|
38
57
|
end
|
39
58
|
|
40
|
-
def
|
41
|
-
|
42
|
-
next if ivar == :@block
|
59
|
+
def restore_ivars(test_object)
|
60
|
+
captured_ivars.each do |(ivar, val)|
|
43
61
|
test_object.instance_variable_set(
|
44
62
|
ivar,
|
45
|
-
|
63
|
+
val
|
46
64
|
)
|
47
65
|
end
|
48
66
|
end
|
@@ -57,22 +75,29 @@ module TestProf
|
|
57
75
|
module ClassMethods
|
58
76
|
attr_accessor :before_all_executor
|
59
77
|
|
60
|
-
def before_all(&block)
|
61
|
-
self.before_all_executor = Executor.new(&block)
|
78
|
+
def before_all(setup_fixtures: BeforeAll.config.setup_fixtures, &block)
|
79
|
+
self.before_all_executor = Executor.new(setup_fixtures: setup_fixtures, &block)
|
62
80
|
|
63
81
|
prepend(Module.new do
|
64
|
-
def
|
65
|
-
self.class.before_all_executor.activate!(self
|
66
|
-
self.class.before_all_executor.restore_to(self)
|
82
|
+
def before_setup
|
83
|
+
self.class.before_all_executor.activate!(self)
|
67
84
|
super
|
68
85
|
end
|
86
|
+
end)
|
69
87
|
|
70
|
-
|
88
|
+
singleton_class.prepend(Module.new do
|
89
|
+
def run(*)
|
71
90
|
super
|
72
|
-
|
91
|
+
ensure
|
92
|
+
before_all_executor&.deactivate!
|
73
93
|
end
|
74
94
|
end)
|
75
95
|
end
|
96
|
+
|
97
|
+
def after_all(&block)
|
98
|
+
self.before_all_executor ||= Executor.new
|
99
|
+
before_all_executor.teardown(&block)
|
100
|
+
end
|
76
101
|
end
|
77
102
|
end
|
78
103
|
end
|