test-prof 0.1.0.pre2 → 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.
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