test-prof 0.1.0.beta1 → 0.1.0.pre5

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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +10 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +69 -0
  5. data/.travis.yml +5 -0
  6. data/Gemfile +4 -0
  7. data/README.md +2 -6
  8. data/Rakefile +8 -0
  9. data/bin/setup +8 -0
  10. data/circle.yml +11 -0
  11. data/guides/any_fixture.md +1 -1
  12. data/guides/stack_prof.md +1 -1
  13. data/lib/test_prof/factory_doctor.rb +9 -11
  14. data/lib/test_prof/ruby_prof.rb +5 -2
  15. data/lib/test_prof/stack_prof.rb +5 -2
  16. data/lib/test_prof/version.rb +1 -1
  17. data/lib/test_prof.rb +1 -19
  18. data/spec/integrations/any_fixture_spec.rb +11 -0
  19. data/spec/integrations/before_all_spec.rb +11 -0
  20. data/spec/integrations/event_prof_spec.rb +100 -0
  21. data/spec/integrations/factory_doctor_spec.rb +20 -0
  22. data/spec/integrations/fixtures/rspec/any_fixture_fixture.rb +37 -0
  23. data/spec/integrations/fixtures/rspec/before_all_fixture.rb +32 -0
  24. data/spec/integrations/fixtures/rspec/event_prof_factory_create_fixture.rb +23 -0
  25. data/spec/integrations/fixtures/rspec/event_prof_fixture.rb +51 -0
  26. data/spec/integrations/fixtures/rspec/event_prof_sidekiq_fixture.rb +53 -0
  27. data/spec/integrations/fixtures/rspec/factory_doctor_fixture.rb +33 -0
  28. data/spec/integrations/fixtures/rspec/rspec_stamp_fixture_tmpl.rb +33 -0
  29. data/spec/integrations/rspec_stamp_spec.rb +53 -0
  30. data/spec/spec_helper.rb +38 -0
  31. data/spec/support/ar_models.rb +43 -0
  32. data/spec/support/instrumenter_stub.rb +19 -0
  33. data/spec/support/integration_helpers.rb +13 -0
  34. data/spec/support/transactional_context.rb +11 -0
  35. data/spec/test_prof/any_fixture_spec.rb +66 -0
  36. data/spec/test_prof/event_prof_spec.rb +138 -0
  37. data/spec/test_prof/ext/float_duration_spec.rb +12 -0
  38. data/spec/test_prof/factory_doctor_spec.rb +84 -0
  39. data/spec/test_prof/rspec_stamp/parser_spec.rb +58 -0
  40. data/spec/test_prof/rspec_stamp_spec.rb +281 -0
  41. data/spec/test_prof/ruby_prof_spec.rb +109 -0
  42. data/spec/test_prof/stack_prof_spec.rb +73 -0
  43. data/spec/test_prof_spec.rb +23 -0
  44. data/test-prof.gemspec +35 -0
  45. metadata +38 -21
  46. data/CHANGELOG.md +0 -7
  47. data/assets/flamegraph.demo.html +0 -173
  48. data/assets/flamegraph.template.html +0 -196
  49. data/assets/src/d3-tip.js +0 -352
  50. data/assets/src/d3-tip.min.js +0 -1
  51. data/assets/src/d3.flameGraph.css +0 -92
  52. data/assets/src/d3.flameGraph.js +0 -459
  53. data/assets/src/d3.flameGraph.min.css +0 -1
  54. data/assets/src/d3.flameGraph.min.js +0 -1
  55. data/assets/src/d3.v4.min.js +0 -8
  56. data/guides/factory_prof.md +0 -85
  57. data/guides/rubocop.md +0 -48
  58. data/lib/test_prof/cops/rspec/aggregate_failures.rb +0 -140
  59. data/lib/test_prof/factory_prof/factory_girl_patch.rb +0 -12
  60. data/lib/test_prof/factory_prof/printers/flamegraph.rb +0 -71
  61. data/lib/test_prof/factory_prof/printers/simple.rb +0 -28
  62. data/lib/test_prof/factory_prof.rb +0 -140
  63. data/lib/test_prof/rubocop.rb +0 -3
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift File.expand_path("../../../../lib", __FILE__)
4
+ require "active_support"
5
+ require "test-prof"
6
+
7
+ shared_context "fixxer", fix: :me do
8
+ before { @value = true }
9
+ end
10
+
11
+ describe "Something" do
12
+ it "fail me" do
13
+ expect(@value).to eq true
14
+ end
15
+
16
+ it "always passes" do
17
+ expect(true).to eq true
18
+ end
19
+
20
+ specify '
21
+ you
22
+ can
23
+ not
24
+ patch me
25
+ ' do
26
+ expect(@value).to eq true
27
+ end
28
+
29
+ context "nested context" do
30
+ subject { @value }
31
+ specify { is_expected.to eq true }
32
+ end
33
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ describe "RSpecStamp" do
6
+ before do
7
+ FileUtils.cp(
8
+ File.expand_path("../../integrations/fixtures/rspec/rspec_stamp_fixture_tmpl.rb", __FILE__),
9
+ File.expand_path("../../integrations/fixtures/rspec/rspec_stamp_fixture.rb", __FILE__)
10
+ )
11
+ end
12
+
13
+ after do
14
+ FileUtils.rm(
15
+ File.expand_path("../../integrations/fixtures/rspec/rspec_stamp_fixture.rb", __FILE__)
16
+ )
17
+ end
18
+
19
+ specify "it works", :aggregate_failures do
20
+ output = run_rspec('rspec_stamp', success: false, env: { 'RSTAMP' => 'fix:me' })
21
+
22
+ expect(output).to include("4 examples, 3 failures")
23
+
24
+ expect(output).to include("RSpec Stamp results")
25
+ expect(output).to include("Total patches: 3")
26
+ expect(output).to include("Total files: 1")
27
+ expect(output).to include("Failed patches: 1")
28
+ expect(output).to include("Ignored files: 0")
29
+
30
+ output2 = run_rspec('rspec_stamp', success: false)
31
+
32
+ expect(output2).to include("4 examples, 1 failure")
33
+ end
34
+
35
+ specify "it works with dry-run", :aggregate_failures do
36
+ output = run_rspec('rspec_stamp', success: false, env: { 'RSTAMP' => 'fix:me', 'RSTAMP_DRY_RUN' => '1' })
37
+
38
+ expect(output).to include("4 examples, 3 failures")
39
+
40
+ expect(output).to include("RSpec Stamp results")
41
+ expect(output).to include("Total patches: 3")
42
+ expect(output).to include("Total files: 1")
43
+ expect(output).to include("Failed patches: 1")
44
+ expect(output).to include("Ignored files: 0")
45
+
46
+ expect(output).to include("(dry-run) Patching ./rspec_stamp_fixture.rb")
47
+ expect(output).to include("Patched: it 'fail me', fix: :me do")
48
+
49
+ output2 = run_rspec('rspec_stamp', success: false)
50
+
51
+ expect(output2).to include("4 examples, 3 failures")
52
+ end
53
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
4
+ require "test-prof"
5
+ require "pry-byebug"
6
+ require "open3"
7
+
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
9
+
10
+ RSpec.configure do |config|
11
+ config.mock_with :rspec
12
+
13
+ config.order = :random
14
+ config.filter_run focus: true
15
+ config.run_all_when_everything_filtered = true
16
+
17
+ config.define_derived_metadata(file_path: %r{/spec/integrations/}) do |metadata|
18
+ metadata[:type] = :integration
19
+ end
20
+
21
+ config.include IntegrationHelpers, type: :integration
22
+
23
+ config.before(:suite) do
24
+ FileUtils.mkdir_p("tmp")
25
+ end
26
+
27
+ config.before(:each) do
28
+ allow(TestProf).to receive(:require).and_return(true)
29
+ # Clear global configuration
30
+ TestProf.remove_instance_variable(:@config) if
31
+ TestProf.instance_variable_defined?(:@config)
32
+ TestProf.config.output = StringIO.new
33
+ end
34
+
35
+ config.after(:suite) do
36
+ FileUtils.rm_rf("tmp")
37
+ end
38
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record"
4
+ require "factory_girl"
5
+
6
+ ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
7
+
8
+ ActiveRecord::Schema.define do
9
+ create_table :users do |t|
10
+ t.string :name
11
+ end
12
+
13
+ create_table :posts do |t|
14
+ t.text :text
15
+ t.integer :user_id
16
+ end
17
+ end
18
+
19
+ class User < ActiveRecord::Base
20
+ validates :name, presence: true
21
+ has_many :posts, dependent: :destroy
22
+
23
+ def clone
24
+ copy = dup
25
+ copy.name = "#{name} (cloned)"
26
+ copy
27
+ end
28
+ end
29
+
30
+ class Post < ActiveRecord::Base
31
+ belongs_to :user
32
+ end
33
+
34
+ FactoryGirl.define do
35
+ factory :user do
36
+ name { |n| "John #{n}" }
37
+ end
38
+
39
+ factory :post do
40
+ text { |n| "Post ##{n}}" }
41
+ user
42
+ end
43
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module InstrumenterStub
4
+ class << self
5
+ def subscribe(event, &block)
6
+ listeners[event] = block
7
+ end
8
+
9
+ def notify(event, time)
10
+ listeners[event].call(time)
11
+ end
12
+
13
+ private
14
+
15
+ def listeners
16
+ @listeners ||= {}
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IntegrationHelpers
4
+ def run_rspec(path, success: true, env: {})
5
+ output, status = Open3.capture2(
6
+ env,
7
+ "rspec #{path}_fixture.rb",
8
+ chdir: File.expand_path("../../integrations/fixtures/rspec", __FILE__)
9
+ )
10
+ expect(status).to be_success if success
11
+ output
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ shared_context "transactional", transactional: true do
4
+ prepend_before(:each) do
5
+ ActiveRecord::Base.connection.begin_transaction(joinable: false)
6
+ end
7
+
8
+ append_after(:each) do
9
+ ActiveRecord::Base.connection.rollback_transaction
10
+ end
11
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require "test_prof/any_fixture"
5
+
6
+ describe TestProf::AnyFixture, :transactional do
7
+ subject { described_class }
8
+
9
+ after { described_class.reset }
10
+
11
+ describe "#register" do
12
+ it "invokes block only once for the same id" do
13
+ block = double('block', call: 1)
14
+ block2 = double('block2', call: 2)
15
+
16
+ expect(block).to receive(:call)
17
+ expect(block2).not_to receive(:call)
18
+
19
+ expect(subject.register(:test) { block.call })
20
+ .to eq 1
21
+
22
+ expect(subject.register(:test) { block2.call })
23
+ .to eq 1
24
+ end
25
+ end
26
+
27
+ describe "#clean" do
28
+ it "tracks AR queries and delete affected tables" do
29
+ # add a record outside of any fixture to check
30
+ # that we delete all records from the tables
31
+ FactoryGirl.create(:user)
32
+
33
+ expect do
34
+ subject.register(:user) { FactoryGirl.create(:user) }
35
+ end.to change(User, :count).by(1)
36
+
37
+ expect do
38
+ subject.register(:post) { FactoryGirl.create(:post) }
39
+ end.to change(User, :count).by(1)
40
+ .and change(Post, :count).by(1)
41
+
42
+ subject.clean
43
+
44
+ # Try to re-register user - should have no effect
45
+ subject.register(:user) { FactoryGirl.create(:user) }
46
+
47
+ expect(User.count).to eq 0
48
+ expect(Post.count).to eq 0
49
+ end
50
+ end
51
+
52
+ describe "#reset" do
53
+ it "delete affected tables and reset cache" do
54
+ expect do
55
+ subject.register(:user) { FactoryGirl.create(:user) }
56
+ end.to change(User, :count).by(1)
57
+
58
+ subject.reset
59
+ expect(User.count).to eq 0
60
+
61
+ subject.register(:user) { FactoryGirl.create(:user) }
62
+
63
+ expect(User.count).to eq 1
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ describe TestProf::EventProf do
6
+ # Use fresh config all for every example
7
+ after { described_class.remove_instance_variable(:@config) }
8
+
9
+ before { stub_const("ActiveSupport::Notifications", double(subscribe: nil)) }
10
+
11
+ subject { described_class.build }
12
+
13
+ describe ".config" do
14
+ specify "defaults", :aggregate_failiures do
15
+ expect(subject.top_count).to eq 5
16
+ expect(subject.rank_by).to eq :time
17
+ end
18
+ end
19
+
20
+ describe ".build" do
21
+ before { described_class.config.event = 'test.event' }
22
+
23
+ it "subscribes to event" do
24
+ expect(TestProf::EventProf::Instrumentations::ActiveSupport)
25
+ .to receive(:subscribe).with('test.event')
26
+ subject
27
+ end
28
+
29
+ it "sets options" do
30
+ expect(subject.event).to eq 'test.event'
31
+ expect(subject.rank_by).to eq :time
32
+ expect(subject.top_count).to eq 5
33
+ end
34
+ end
35
+
36
+ describe "#result" do
37
+ let(:results) do
38
+ described_class.config.event = 'test.event'
39
+ described_class.config.instrumenter = InstrumenterStub
40
+
41
+ subject
42
+
43
+ subject.group_started 'A'
44
+
45
+ subject.example_started 'A1'
46
+ InstrumenterStub.notify 'test.event', 100
47
+ subject.example_finished 'A1'
48
+
49
+ subject.group_finished 'A'
50
+
51
+ subject.group_started 'B'
52
+
53
+ subject.example_started 'B1'
54
+ InstrumenterStub.notify 'test.event', 140
55
+ InstrumenterStub.notify 'test.event', 240
56
+ subject.example_finished 'B1'
57
+
58
+ subject.example_started 'B2'
59
+ InstrumenterStub.notify 'test.event', 40
60
+ subject.example_finished 'B2'
61
+
62
+ subject.group_finished 'B'
63
+
64
+ subject.group_started 'C'
65
+
66
+ subject.example_started 'C1'
67
+ InstrumenterStub.notify 'test.event', 400
68
+ InstrumenterStub.notify 'test.event', 40
69
+ subject.example_finished 'C1'
70
+
71
+ subject.example_started 'C2'
72
+ subject.example_finished 'C2'
73
+
74
+ subject.group_finished 'C'
75
+
76
+ subject.results
77
+ end
78
+
79
+ it "returns top slow groups and totals" do
80
+ expect(results).to eq(
81
+ groups: [
82
+ { id: 'C', examples: 2, time: 440, count: 2 },
83
+ { id: 'B', examples: 2, time: 420, count: 3 },
84
+ { id: 'A', examples: 1, time: 100, count: 1 }
85
+ ]
86
+ )
87
+ expect(subject.total_time).to eq 960
88
+ expect(subject.total_count).to eq 6
89
+ end
90
+
91
+ context "when rank by count" do
92
+ before { described_class.config.rank_by = :count }
93
+
94
+ it "returns top groups by event occurances" do
95
+ expect(results).to eq(
96
+ groups: [
97
+ { id: 'B', examples: 2, time: 420, count: 3 },
98
+ { id: 'C', examples: 2, time: 440, count: 2 },
99
+ { id: 'A', examples: 1, time: 100, count: 1 }
100
+ ]
101
+ )
102
+ end
103
+ end
104
+
105
+ context "when top_count is specified" do
106
+ before { described_class.config.top_count = 2 }
107
+
108
+ it "returns top groups by event occurances" do
109
+ expect(results).to eq(
110
+ groups: [
111
+ { id: 'C', examples: 2, time: 440, count: 2 },
112
+ { id: 'B', examples: 2, time: 420, count: 3 }
113
+ ]
114
+ )
115
+ end
116
+ end
117
+
118
+ context "when per_example is true" do
119
+ before { described_class.config.per_example = true }
120
+
121
+ it "returns top groups and examples" do
122
+ expect(results).to eq(
123
+ groups: [
124
+ { id: 'C', examples: 2, time: 440, count: 2 },
125
+ { id: 'B', examples: 2, time: 420, count: 3 },
126
+ { id: 'A', examples: 1, time: 100, count: 1 }
127
+ ],
128
+ examples: [
129
+ { id: 'C1', time: 440, count: 2 },
130
+ { id: 'B1', time: 380, count: 2 },
131
+ { id: 'A1', time: 100, count: 1 },
132
+ { id: 'B2', time: 40, count: 1 }
133
+ ]
134
+ )
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require "test_prof/ext/float_duration"
5
+
6
+ using TestProf::FloatDuration
7
+
8
+ describe TestProf::FloatDuration do
9
+ it "works" do
10
+ expect((27 * 60 + 41.05142).duration).to eq "27:41.051"
11
+ end
12
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ # Init FactoryDoctor and patch FactoryGirl
6
+ TestProf::FactoryDoctor.init
7
+
8
+ describe TestProf::FactoryDoctor, :transactional do
9
+ before { described_class.start }
10
+ after { described_class.stop }
11
+
12
+ # Ensure meta-queries have been performed
13
+ before(:all) { User.first }
14
+
15
+ describe "#result" do
16
+ subject(:result) { described_class.result }
17
+
18
+ it "is not bad when nothing created" do
19
+ FactoryGirl.build_stubbed(:user)
20
+ User.first
21
+ expect(result).not_to be_bad
22
+ expect(result.count).to eq 0
23
+ expect(result.time).to eq 0
24
+ expect(result.queries_count).to eq 1
25
+ end
26
+
27
+ it "detects one useless object" do
28
+ FactoryGirl.create(:user)
29
+ expect(result).to be_bad
30
+ expect(result.count).to eq 1
31
+ expect(result.time).to be > 0
32
+ end
33
+
34
+ it "detects not useless object when select" do
35
+ user = FactoryGirl.create(:user)
36
+ user.reload
37
+
38
+ expect(result).not_to be_bad
39
+ expect(result.count).to eq 1
40
+ expect(result.queries_count).to eq 1
41
+ expect(result.time).to be > 0
42
+ end
43
+
44
+ it "detects not useless object when update" do
45
+ user = FactoryGirl.create(:user)
46
+ user.update!(name: 'Phil')
47
+
48
+ expect(result).not_to be_bad
49
+ expect(result.count).to eq 1
50
+ expect(result.queries_count).to eq 1
51
+ expect(result.time).to be > 0
52
+ end
53
+
54
+ it "detects many objects" do
55
+ FactoryGirl.create_pair(:user)
56
+
57
+ expect(result).to be_bad
58
+ expect(result.count).to eq 2
59
+ expect(result.time).to be > 0
60
+ end
61
+
62
+ describe "#ignore" do
63
+ it "does not track create" do
64
+ described_class.ignore do
65
+ FactoryGirl.create(:user)
66
+ end
67
+
68
+ expect(result).not_to be_bad
69
+ expect(result.count).to eq 0
70
+ expect(result.time).to eq 0
71
+ end
72
+
73
+ it "does not track queries" do
74
+ user = FactoryGirl.create(:user)
75
+
76
+ described_class.ignore { user.reload }
77
+
78
+ expect(result).to be_bad
79
+ expect(result.count).to eq 1
80
+ expect(result.time).to be > 0
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require "test_prof/rspec_stamp/parser"
5
+
6
+ describe TestProf::RSpecStamp::Parser do
7
+ subject { described_class }
8
+
9
+ describe ".parse" do
10
+ it "handles simple expr" do
11
+ res = subject.parse('it "works"')
12
+ expect(res.fname).to eq 'it'
13
+ expect(res.desc).to eq 'works'
14
+ expect(res.tags).to be_nil
15
+ expect(res.htags).to be_nil
16
+ end
17
+
18
+ it "handles missing desc" do
19
+ res = subject.parse('it ')
20
+ expect(res.fname).to eq 'it'
21
+ expect(res.desc).to be_nil
22
+ expect(res.tags).to be_nil
23
+ expect(res.htags).to be_nil
24
+ end
25
+
26
+ it "handles parentheses" do
27
+ res = subject.parse(' it("is") ')
28
+ expect(res.fname).to eq 'it'
29
+ expect(res.desc).to eq 'is'
30
+ expect(res.tags).to be_nil
31
+ expect(res.htags).to be_nil
32
+ end
33
+
34
+ it "handles several args" do
35
+ res = subject.parse(' it "is o\'h", :cool, :bad ')
36
+ expect(res.fname).to eq 'it'
37
+ expect(res.desc).to eq "is o'h"
38
+ expect(res.tags).to eq(%i[cool bad])
39
+ expect(res.htags).to be_nil
40
+ end
41
+
42
+ it "handles hargs" do
43
+ res = subject.parse(' it "is", cool: :bad, type: "feature" ')
44
+ expect(res.fname).to eq 'it'
45
+ expect(res.desc).to eq "is"
46
+ expect(res.tags).to be_nil
47
+ expect(res.htags).to eq([%i[cool bad], [:type, "feature"]])
48
+ end
49
+
50
+ it "handles args and hargs" do
51
+ res = subject.parse(' it "is", :cool, :bad, type: :feature ')
52
+ expect(res.fname).to eq 'it'
53
+ expect(res.desc).to eq "is"
54
+ expect(res.tags).to eq(%i[cool bad])
55
+ expect(res.htags).to eq([%i[type feature]])
56
+ end
57
+ end
58
+ end