test-prof 0.1.0.pre2 → 0.1.0.pre5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 528abbfd18c27537ab52c1bf580c73b4327fdbbc
4
- data.tar.gz: dfaaa8adc9c93ec2ba4e562cf57ec3f4807ae489
3
+ metadata.gz: c9b58b9e9898a5b72c42700bfc0751c31639aefc
4
+ data.tar.gz: 4bf4799f92e06e8e9b3c000fe08659171cd2293d
5
5
  SHA512:
6
- metadata.gz: db4e05c01a501eb9e9a50d8c4d1f857437ca7c14323c7a25e739736e1139c8201c87fbefcff91c84cf0932c475e9fc2f12006f3f7feb9d6d5068d23b492bdf61
7
- data.tar.gz: 2ba7680b870ef1acf274cabb3fd0f4bdfc73558af1d3211e2808f2e99f7864c212212fce9c7ef6f68f78d55f7d5cba1ff44674273fdb6bedfce3c135c77af2d4
6
+ metadata.gz: 50f89b1bce0e4c45bb00aacaff4cfb6f1e1f8f2875cc0fc38f2317244c662b24f8b56788bb1980221b5ca6987753b3b190a3b9e671de958f5a581a1616ca0fe7
7
+ data.tar.gz: ed479e4104b92b159a7872fc7dbb85e6b69308c954dade0ca2cb7d044bc60ef11f483e905cb569ceaa0b686a7b07bbc49de841f3424f3b4535f01acc9f985167
data/README.md CHANGED
@@ -65,6 +65,8 @@ We also want to share some small code tricks which can help you to improve your
65
65
 
66
66
  - [AnyFixture](https://github.com/palkan/test-prof/tree/master/guides/any_fixture.md)
67
67
 
68
+ - [RSpec Stamp](https://github.com/palkan/test-prof/tree/master/guides/rspec_stamp.md)
69
+
68
70
  ## Configuration
69
71
 
70
72
  TestProf global configuration is used by most of the profilers:
@@ -0,0 +1,53 @@
1
+ # RSpec Stamp
2
+
3
+ RSpec Stamp is a tool to automatically _tag_ failed examples with custom tags.
4
+
5
+ It _literally_ adds tags to your examples (i.e. rewrites them).
6
+
7
+ The main purpose of RSpec Stamp is to make refactoring testing codebase easy. Changing global configuration may cause a lot of failures. You can patch failing spec by adding a shared context. And here comes RSpec Stamp.
8
+
9
+ ## Example Use Case: Sidekiq Inline
10
+
11
+ Using `Sidekiq::Testing.inline!` may be considered a _bad practice_ (see [here](https://github.com/mperham/sidekiq/issues/3495)) due to its negative performance impact. But it's still widely used.
12
+
13
+ How to migrate from `inline!` to `fake!`?
14
+
15
+ Step 0. Make sure that all your tests pass.
16
+
17
+ Step 1. Create a shared context to conditionally turn on `inline!` mode:
18
+
19
+ ```ruby
20
+ shared_context "sidekiq:inline", sidekiq: :inline do
21
+ around(:each) { |ex| Sidekiq::Testing.inline!(&ex) }
22
+ end
23
+ ```
24
+
25
+ Step 2. Turn on `fake!` mode globally.
26
+
27
+ Step 3. Run `RSTAMP=sidekiq:inline rspec`.
28
+
29
+ The output of the command above contains information about the _stamping_ process:
30
+
31
+ - How many files have been affected?
32
+
33
+ - How many patches were made?
34
+
35
+ - How many patches failed?
36
+
37
+ - How many files have been ignored?
38
+
39
+ Now all (or almost all) failing specs are tagged with `sidekiq: :inline`. Run the whole suite again and check it there are any failures left.
40
+
41
+ There is also a _dry-run_ mode (activated by `RSTAMP_DRY_RUN=1` env variable) which prints out patches instead of re-writing files.
42
+
43
+ ## Configuration
44
+
45
+ By default, RSpecStamp ignores examples located in `spec/support` directory (typical place to put shared examples in).
46
+ You can add more _ignore_ patterns:
47
+
48
+ ```ruby
49
+ TestProf::RSpecStamp.configure do |config|
50
+ config.ignore_files << %r{spec/my_directory}
51
+ end
52
+ ```
53
+
data/lib/test_prof.rb CHANGED
@@ -47,8 +47,8 @@ module TestProf
47
47
  def activate(env_var)
48
48
  if defined?(::Spring) && !ENV['DISABLE_SPRING']
49
49
  Spring.after_fork { yield if ENV[env_var] }
50
- else
51
- yield if ENV[env_var]
50
+ elsif ENV[env_var]
51
+ yield
52
52
  end
53
53
  end
54
54
 
@@ -87,3 +87,4 @@ require "test_prof/ruby_prof"
87
87
  require "test_prof/stack_prof"
88
88
  require "test_prof/event_prof"
89
89
  require "test_prof/factory_doctor"
90
+ require "test_prof/rspec_stamp"
@@ -65,9 +65,7 @@ module TestProf
65
65
  def build
66
66
  Profiler.new(
67
67
  event: config.event,
68
- instrumenter: config.resolve_instrumenter,
69
- rank_by: config.rank_by,
70
- top_count: config.top_count
68
+ instrumenter: config.resolve_instrumenter
71
69
  )
72
70
  end
73
71
  end
@@ -77,7 +75,7 @@ module TestProf
77
75
 
78
76
  attr_reader :event, :top_count, :rank_by, :total_count, :total_time
79
77
 
80
- def initialize(event:, instrumenter:, rank_by:, top_count:)
78
+ def initialize(event:, instrumenter:)
81
79
  @event = event
82
80
 
83
81
  log :info, "EventProf enabled (#{@event})"
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TestProf::EventProf::CustomEvents
4
- module FactoryCreate
4
+ module FactoryCreate # :nodoc: all
5
5
  module RunnerPatch
6
6
  def run(strategy = @strategy)
7
7
  return super unless strategy == :create
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TestProf::EventProf::CustomEvents
4
- module SidekiqInline
4
+ module SidekiqInline # :nodoc: all
5
5
  module ClientPatch
6
6
  def raw_push(*)
7
7
  return super unless Sidekiq::Testing.inline?
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TestProf::EventProf::CustomEvents
4
- module SidekiqJobs
4
+ module SidekiqJobs # :nodoc: all
5
5
  module ClientPatch
6
6
  def raw_push(*)
7
7
  return super unless Sidekiq::Testing.inline?
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_prof/logging"
4
+ require "test_prof/rspec_stamp/parser"
5
+
6
+ module TestProf
7
+ # Mark RSpec examples with provided tags
8
+ module RSpecStamp
9
+ EXAMPLE_RXP = /(\s*)(\w+\s*(?:.*)\s*)(do|{)/
10
+
11
+ # RSpecStamp configuration
12
+ class Configuration
13
+ attr_accessor :ignore_files, :dry_run, :tags
14
+
15
+ def initialize
16
+ @ignore_files = [%r{spec/support}]
17
+ @dry_run = ENV['RSTAMP_DRY_RUN'] == '1'
18
+ self.tags = ENV['RSTAMP']
19
+ end
20
+
21
+ def dry_run?
22
+ @dry_run == true
23
+ end
24
+
25
+ def tags=(val)
26
+ @tags = if val.is_a?(String)
27
+ parse_tags(val)
28
+ else
29
+ val
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def parse_tags(str)
36
+ str.split(/\s*,\s*/).each_with_object([]) do |tag, acc|
37
+ k, v = tag.split(":")
38
+ acc << if v.nil?
39
+ k.to_sym
40
+ else
41
+ Hash[k.to_sym, v.to_sym]
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ class << self
48
+ include TestProf::Logging
49
+
50
+ def config
51
+ @config ||= Configuration.new
52
+ end
53
+
54
+ def configure
55
+ yield config
56
+ end
57
+
58
+ # Accepts source code (as array of lines),
59
+ # line numbers (of example to apply tags)
60
+ # and an array of tags.
61
+ def apply_tags(code, lines, tags)
62
+ failed = 0
63
+
64
+ lines.each do |line|
65
+ unless stamp_example(code[line - 1], tags)
66
+ failed += 1
67
+ log :warn, "Failed to stamp: #{code[line - 1]}"
68
+ end
69
+ end
70
+ failed
71
+ end
72
+
73
+ private
74
+
75
+ # rubocop: disable Metrics/CyclomaticComplexity
76
+ # rubocop: disable Metrics/PerceivedComplexity
77
+ def stamp_example(example, tags)
78
+ matches = example.match(EXAMPLE_RXP)
79
+ return false unless matches
80
+
81
+ code = matches[2]
82
+ block = matches[3]
83
+
84
+ parsed = Parser.parse(code)
85
+ return false unless parsed
86
+
87
+ parsed.desc ||= 'works'
88
+
89
+ tags.each do |t|
90
+ if t.is_a?(Hash)
91
+ t.keys.each { |k| parsed.add_htag(k, t[k]) }
92
+ else
93
+ parsed.add_tag(t)
94
+ end
95
+ end
96
+
97
+ need_parens = block == "{"
98
+
99
+ tags_str = parsed.tags.map { |t| t.is_a?(Symbol) ? ":#{t}" : t }.join(", ") unless
100
+ parsed.tags.nil?
101
+
102
+ unless parsed.htags.nil?
103
+ htags_str = parsed.htags.map do |(k, v)|
104
+ vstr = v.is_a?(Symbol) ? ":#{v}" : quote(v)
105
+
106
+ "#{k}: #{vstr}"
107
+ end
108
+ end
109
+
110
+ replacement = "\\1#{parsed.fname}#{need_parens ? '(' : ' '}"\
111
+ "#{[quote(parsed.desc), tags_str, htags_str].compact.join(', ')}"\
112
+ "#{need_parens ? ') ' : ' '}\\3"
113
+
114
+ if config.dry_run?
115
+ log :info, "Patched: #{example.sub(EXAMPLE_RXP, replacement)}"
116
+ else
117
+ example.sub!(EXAMPLE_RXP, replacement)
118
+ end
119
+ true
120
+ end
121
+ # rubocop: enable Metrics/CyclomaticComplexity
122
+ # rubocop: enable Metrics/PerceivedComplexity
123
+
124
+ def quote(str)
125
+ if str.include?("'")
126
+ "\"#{str}\""
127
+ else
128
+ "'#{str}'"
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
134
+
135
+ require "test_prof/rspec_stamp/rspec" if defined?(RSpec)
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ripper"
4
+
5
+ # rubocop: disable Metrics/CyclomaticComplexity
6
+
7
+ module TestProf
8
+ module RSpecStamp
9
+ # Parse examples headers
10
+ module Parser
11
+ # Contains the result of parsing
12
+ class Result
13
+ attr_accessor :fname, :desc
14
+ attr_reader :tags, :htags
15
+
16
+ def add_tag(v)
17
+ @tags ||= []
18
+ @tags << v
19
+ end
20
+
21
+ def add_htag(k, v)
22
+ @htags ||= []
23
+ @htags << [k, v]
24
+ end
25
+ end
26
+
27
+ class << self
28
+ def parse(code)
29
+ sexp = Ripper.sexp(code)
30
+ return unless sexp
31
+
32
+ # sexp has the following format:
33
+ # [:program,
34
+ # [
35
+ # [
36
+ # :command,
37
+ # [:@ident, "it", [1, 0]],
38
+ # [:args_add_block, [ ... ]]
39
+ # ]
40
+ # ]
41
+ # ]
42
+ #
43
+ # or
44
+ #
45
+ # [:program,
46
+ # [
47
+ # [
48
+ # :vcall,
49
+ # [:@ident, "it", [1, 0]]
50
+ # ]
51
+ # ]
52
+ # ]
53
+ res = Result.new
54
+
55
+ fcall = sexp[1][0][1]
56
+ fcall = fcall[1] if fcall.first == :fcall
57
+ res.fname = fcall[1]
58
+
59
+ args_block = sexp[1][0][2]
60
+
61
+ return res if args_block.nil?
62
+
63
+ args_block = args_block[1] if args_block.first == :arg_paren
64
+
65
+ args = args_block[1]
66
+
67
+ if args.first.first == :string_literal
68
+ res.desc = parse_literal(args.shift)
69
+ end
70
+
71
+ parse_arg(res, args.shift) until args.empty?
72
+
73
+ res
74
+ end
75
+
76
+ private
77
+
78
+ def parse_arg(res, arg)
79
+ if arg.first == :symbol_literal
80
+ res.add_tag parse_literal(arg)
81
+ elsif arg.first == :bare_assoc_hash
82
+ parse_hash(res, arg[1])
83
+ end
84
+ end
85
+
86
+ def parse_hash(res, hash_arg)
87
+ hash_arg.each do |(_, label, val)|
88
+ res.add_htag label[1][0..-2].to_sym, parse_literal(val)
89
+ end
90
+ end
91
+
92
+ # Expr of the form:
93
+ # [:string_literal, [:string_content, [:@tstring_content, "is", [1, 4]]]]
94
+ def parse_literal(expr)
95
+ val = expr[1][1][1]
96
+ val = val.to_sym if expr[0] == :symbol_literal ||
97
+ expr[0] == :assoc_new
98
+ val
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TestProf
4
+ module RSpecStamp
5
+ class RSpecListener # :nodoc:
6
+ include Logging
7
+
8
+ NOTIFICATIONS = %i[
9
+ example_failed
10
+ ].freeze
11
+
12
+ def initialize
13
+ @failed = 0
14
+ @ignored = 0
15
+ @total = 0
16
+ @examples = Hash.new { |h, k| h[k] = [] }
17
+ end
18
+
19
+ def example_failed(notification)
20
+ return if notification.example.pending?
21
+
22
+ location = notification.example.metadata[:location]
23
+
24
+ file, line = location.split(":")
25
+
26
+ @examples[file] << line.to_i
27
+ end
28
+
29
+ def stamp!
30
+ @examples.each do |file, lines|
31
+ stamp_file(file, lines.uniq)
32
+ end
33
+
34
+ msgs = []
35
+
36
+ msgs <<
37
+ <<~MSG
38
+ RSpec Stamp results
39
+
40
+ Total patches: #{@total}
41
+ Total files: #{@examples.keys.size}
42
+
43
+ Failed patches: #{@failed}
44
+ Ignored files: #{@ignored}
45
+ MSG
46
+
47
+ log :info, msgs.join
48
+ end
49
+
50
+ private
51
+
52
+ def stamp_file(file, lines)
53
+ @total += lines.size
54
+ return if ignored?(file)
55
+
56
+ log :info, "(dry-run) Patching #{file}" if dry_run?
57
+
58
+ code = File.readlines(file)
59
+
60
+ @failed += RSpecStamp.apply_tags(code, lines, RSpecStamp.config.tags)
61
+
62
+ File.write(file, code.join) unless dry_run?
63
+ end
64
+
65
+ def ignored?(file)
66
+ ignored = RSpecStamp.config.ignore_files.find do |pattern|
67
+ file =~ pattern
68
+ end
69
+
70
+ return unless ignored
71
+ log :warn, "Ignore stamping file: #{file}"
72
+ @ignored += 1
73
+ end
74
+
75
+ def dry_run?
76
+ RSpecStamp.config.dry_run?
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ # Register EventProf listener
83
+ TestProf.activate('RSTAMP') do
84
+ RSpec.configure do |config|
85
+ listener = TestProf::RSpecStamp::RSpecListener.new
86
+
87
+ config.reporter.register_listener(listener, *TestProf::RSpecStamp::RSpecListener::NOTIFICATIONS)
88
+
89
+ config.after(:suite) { listener.stamp! }
90
+ end
91
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TestProf
4
- VERSION = "0.1.0.pre2"
4
+ VERSION = "0.1.0.pre5"
5
5
  end
@@ -77,8 +77,8 @@ describe "EventProf" do
77
77
  expect(output).to match(/Total time: \d{2}:\d{2}\.\d{3}/)
78
78
  expect(output).to include("Total events: 3")
79
79
 
80
- expect(output).to match(%r{SingleJob \(./event_prof_sidekiq_fixture.rb:28\) – \d{2}:\d{2}.\d{3} \(2 / 2\)})
81
- expect(output).to match(%r{BatchJob \(./event_prof_sidekiq_fixture.rb:40\) – \d{2}:\d{2}.\d{3} \(1 / 2\)})
80
+ expect(output).to match(%r{SingleJob \(./event_prof_sidekiq_fixture.rb:27\) – \d{2}:\d{2}.\d{3} \(2 / 2\)})
81
+ expect(output).to match(%r{BatchJob \(./event_prof_sidekiq_fixture.rb:39\) – \d{2}:\d{2}.\d{3} \(1 / 2\)})
82
82
  end
83
83
 
84
84
  it "works with sidekiq.jobs" do
@@ -93,8 +93,8 @@ describe "EventProf" do
93
93
 
94
94
  expect(output).to include("Top 5 slowest suites (by count):")
95
95
 
96
- expect(output).to match(%r{SingleJob \(./event_prof_sidekiq_fixture.rb:28\) – \d{2}:\d{2}.\d{3} \(2 / 2\)})
97
- expect(output).to match(%r{BatchJob \(./event_prof_sidekiq_fixture.rb:40\) – \d{2}:\d{2}.\d{3} \(4 / 2\)})
96
+ expect(output).to match(%r{SingleJob \(./event_prof_sidekiq_fixture.rb:27\) – \d{2}:\d{2}.\d{3} \(2 / 2\)})
97
+ expect(output).to match(%r{BatchJob \(./event_prof_sidekiq_fixture.rb:39\) – \d{2}:\d{2}.\d{3} \(4 / 2\)})
98
98
  end
99
99
  end
100
100
  end
@@ -9,12 +9,11 @@ Sidekiq::Testing.inline!
9
9
  class SingleJob
10
10
  include Sidekiq::Worker
11
11
 
12
- def perform(*args)
13
- 1 == 1
12
+ def perform(*_args)
13
+ true
14
14
  end
15
15
  end
16
16
 
17
-
18
17
  class BatchJob
19
18
  include Sidekiq::Worker
20
19
 
@@ -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
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module IntegrationHelpers
4
- def run_rspec(path, env: {})
4
+ def run_rspec(path, success: true, env: {})
5
5
  output, status = Open3.capture2(
6
6
  env,
7
7
  "rspec #{path}_fixture.rb",
8
8
  chdir: File.expand_path("../../integrations/fixtures/rspec", __FILE__)
9
9
  )
10
- expect(status).to be_success
10
+ expect(status).to be_success if success
11
11
  output
12
12
  end
13
13
  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
@@ -0,0 +1,281 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require "test_prof/rspec_stamp"
5
+
6
+ describe TestProf::RSpecStamp do
7
+ subject { described_class }
8
+
9
+ describe "#config" do
10
+ after { described_class.remove_instance_variable(:@config) }
11
+
12
+ subject { described_class.config }
13
+
14
+ it "handles array tags" do
15
+ subject.tags = [:todo]
16
+ expect(subject.tags).to eq([:todo])
17
+ end
18
+
19
+ it "handles string tags" do
20
+ subject.tags = "todo"
21
+ expect(subject.tags).to eq([:todo])
22
+ end
23
+
24
+ it "handle string hash tags" do
25
+ subject.tags = "fix:me"
26
+ expect(subject.tags).to eq([{ fix: :me }])
27
+ end
28
+
29
+ it "handle several tags" do
30
+ subject.tags = "todo,fix:me"
31
+ expect(subject.tags).to eq([:todo, { fix: :me }])
32
+ end
33
+ end
34
+
35
+ describe ".apply_tags" do
36
+ let(:code) { source.split("\n") }
37
+
38
+ let(:lines) { [1] }
39
+
40
+ let(:tags) { [:todo] }
41
+
42
+ subject { described_class.apply_tags(code, lines, tags) }
43
+
44
+ let(:source) do
45
+ <<~CODE
46
+ it "doesn't do what it should do" do
47
+ expect(subject.body).to eq("OK")
48
+ end
49
+ CODE
50
+ end
51
+
52
+ let(:expected) do
53
+ <<~CODE
54
+ it "doesn't do what it should do", :todo do
55
+ expect(subject.body).to eq("OK")
56
+ end
57
+ CODE
58
+ end
59
+
60
+ specify do
61
+ is_expected.to eq 0
62
+ expect(code.join("\n")).to eq expected.strip
63
+ end
64
+
65
+ context "with several examples" do
66
+ let(:source) do
67
+ <<~CODE
68
+ it 'succeeds' do
69
+ expect(subject.body).to eq("OK")
70
+ end
71
+
72
+ context "not found" do
73
+ let(:post) { draft_post }
74
+
75
+ it 'fails' do
76
+ expect(subject.body).to eq("Not Found")
77
+ end
78
+ end
79
+ CODE
80
+ end
81
+
82
+ let(:expected) do
83
+ <<~CODE
84
+ it 'succeeds' do
85
+ expect(subject.body).to eq("OK")
86
+ end
87
+
88
+ context "not found" do
89
+ let(:post) { draft_post }
90
+
91
+ it 'fails', :todo do
92
+ expect(subject.body).to eq("Not Found")
93
+ end
94
+ end
95
+ CODE
96
+ end
97
+
98
+ let(:lines) { [8] }
99
+
100
+ specify do
101
+ is_expected.to eq 0
102
+ expect(code.join("\n")).to eq expected.strip
103
+ end
104
+
105
+ context "patch all" do
106
+ let(:expected) do
107
+ <<~CODE
108
+ it 'succeeds', :todo do
109
+ expect(subject.body).to eq("OK")
110
+ end
111
+
112
+ context "not found" do
113
+ let(:post) { draft_post }
114
+
115
+ it 'fails', :todo do
116
+ expect(subject.body).to eq("Not Found")
117
+ end
118
+ end
119
+ CODE
120
+ end
121
+
122
+ let(:lines) { [1, 8] }
123
+
124
+ specify do
125
+ is_expected.to eq 0
126
+ expect(code.join("\n")).to eq expected.strip
127
+ end
128
+ end
129
+ end
130
+
131
+ context "without description" do
132
+ let(:source) do
133
+ <<~CODE
134
+ specify do
135
+ expect(subject.body).to eq("Not Found")
136
+ end
137
+ CODE
138
+ end
139
+
140
+ let(:expected) do
141
+ <<~CODE
142
+ specify 'works', :todo do
143
+ expect(subject.body).to eq("Not Found")
144
+ end
145
+ CODE
146
+ end
147
+
148
+ specify do
149
+ is_expected.to eq 0
150
+ expect(code.join("\n")).to eq expected.strip
151
+ end
152
+ end
153
+
154
+ context "one-liner" do
155
+ let(:source) do
156
+ <<~CODE
157
+ it("is") { expect(subject.body).to eq("Not Found") }
158
+ CODE
159
+ end
160
+
161
+ let(:expected) do
162
+ <<~CODE
163
+ it('is', :todo) { expect(subject.body).to eq("Not Found") }
164
+ CODE
165
+ end
166
+
167
+ specify do
168
+ is_expected.to eq 0
169
+ expect(code.join("\n")).to eq expected.strip
170
+ end
171
+ end
172
+
173
+ context "one-liner without description" do
174
+ let(:source) do
175
+ <<~CODE
176
+ it { expect(subject.body).to eq("Not Found") }
177
+ CODE
178
+ end
179
+
180
+ let(:expected) do
181
+ <<~CODE
182
+ it('works', :todo) { expect(subject.body).to eq("Not Found") }
183
+ CODE
184
+ end
185
+
186
+ specify do
187
+ is_expected.to eq 0
188
+ expect(code.join("\n")).to eq expected.strip
189
+ end
190
+ end
191
+
192
+ context "with existing tags" do
193
+ let(:source) do
194
+ <<~CODE
195
+ it 'is "KOI"', :log do
196
+ expect(subject.body).to eq("Not Found")
197
+ end
198
+ CODE
199
+ end
200
+
201
+ let(:expected) do
202
+ <<~CODE
203
+ it 'is "KOI"', :log, :todo do
204
+ expect(subject.body).to eq("Not Found")
205
+ end
206
+ CODE
207
+ end
208
+
209
+ specify do
210
+ is_expected.to eq 0
211
+ expect(code.join("\n")).to eq expected.strip
212
+ end
213
+ end
214
+
215
+ context "with several tags" do
216
+ let(:source) do
217
+ <<~CODE
218
+ specify do
219
+ expect(subject.body).to eq("Not Found")
220
+ end
221
+ CODE
222
+ end
223
+
224
+ let(:expected) do
225
+ <<~CODE
226
+ specify 'works', :todo, a: :b, c: 'd' do
227
+ expect(subject.body).to eq("Not Found")
228
+ end
229
+ CODE
230
+ end
231
+
232
+ let(:tags) { [{ a: :b }, :todo, { c: 'd' }] }
233
+
234
+ specify do
235
+ is_expected.to eq 0
236
+ expect(code.join("\n")).to eq expected.strip
237
+ end
238
+
239
+ context "with existing tags" do
240
+ let(:source) do
241
+ <<~CODE
242
+ it 'is', :log, level: :debug do
243
+ expect(subject.body).to eq("Not Found")
244
+ end
245
+ CODE
246
+ end
247
+
248
+ let(:expected) do
249
+ <<~CODE
250
+ it 'is', :log, :todo, level: :debug, a: :b, c: 'd' do
251
+ expect(subject.body).to eq("Not Found")
252
+ end
253
+ CODE
254
+ end
255
+
256
+ specify do
257
+ is_expected.to eq 0
258
+ expect(code.join("\n")).to eq expected.strip
259
+ end
260
+ end
261
+ end
262
+
263
+ context "with multiline description" do
264
+ let(:source) do
265
+ <<~CODE
266
+ it %q{
267
+ succeeds
268
+ this time
269
+ } do
270
+ expect(subject.body).to eq("OK")
271
+ end
272
+ CODE
273
+ end
274
+
275
+ specify do
276
+ is_expected.to eq 1
277
+ expect(code.join("\n")).to eq source.strip
278
+ end
279
+ end
280
+ end
281
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: test-prof
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.pre2
4
+ version: 0.1.0.pre5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Dementyev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-06-25 00:00:00.000000000 Z
11
+ date: 2017-06-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -160,6 +160,7 @@ files:
160
160
  - guides/before_all.md
161
161
  - guides/event_prof.md
162
162
  - guides/factory_doctor.md
163
+ - guides/rspec_stamp.md
163
164
  - guides/ruby_prof.md
164
165
  - guides/stack_prof.md
165
166
  - lib/test-prof.rb
@@ -181,6 +182,9 @@ files:
181
182
  - lib/test_prof/logging.rb
182
183
  - lib/test_prof/recipes/rspec/any_fixture.rb
183
184
  - lib/test_prof/recipes/rspec/before_all.rb
185
+ - lib/test_prof/rspec_stamp.rb
186
+ - lib/test_prof/rspec_stamp/parser.rb
187
+ - lib/test_prof/rspec_stamp/rspec.rb
184
188
  - lib/test_prof/ruby_prof.rb
185
189
  - lib/test_prof/ruby_prof/rspec.rb
186
190
  - lib/test_prof/stack_prof.rb
@@ -196,6 +200,8 @@ files:
196
200
  - spec/integrations/fixtures/rspec/event_prof_fixture.rb
197
201
  - spec/integrations/fixtures/rspec/event_prof_sidekiq_fixture.rb
198
202
  - spec/integrations/fixtures/rspec/factory_doctor_fixture.rb
203
+ - spec/integrations/fixtures/rspec/rspec_stamp_fixture_tmpl.rb
204
+ - spec/integrations/rspec_stamp_spec.rb
199
205
  - spec/spec_helper.rb
200
206
  - spec/support/ar_models.rb
201
207
  - spec/support/instrumenter_stub.rb
@@ -205,6 +211,8 @@ files:
205
211
  - spec/test_prof/event_prof_spec.rb
206
212
  - spec/test_prof/ext/float_duration_spec.rb
207
213
  - spec/test_prof/factory_doctor_spec.rb
214
+ - spec/test_prof/rspec_stamp/parser_spec.rb
215
+ - spec/test_prof/rspec_stamp_spec.rb
208
216
  - spec/test_prof/ruby_prof_spec.rb
209
217
  - spec/test_prof/stack_prof_spec.rb
210
218
  - spec/test_prof_spec.rb