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 +4 -4
- data/README.md +2 -0
- data/guides/rspec_stamp.md +53 -0
- data/lib/test_prof.rb +3 -2
- data/lib/test_prof/event_prof.rb +2 -4
- data/lib/test_prof/event_prof/custom_events/factory_create.rb +1 -1
- data/lib/test_prof/event_prof/custom_events/sidekiq_inline.rb +1 -1
- data/lib/test_prof/event_prof/custom_events/sidekiq_jobs.rb +1 -1
- data/lib/test_prof/rspec_stamp.rb +135 -0
- data/lib/test_prof/rspec_stamp/parser.rb +103 -0
- data/lib/test_prof/rspec_stamp/rspec.rb +91 -0
- data/lib/test_prof/version.rb +1 -1
- data/spec/integrations/event_prof_spec.rb +4 -4
- data/spec/integrations/fixtures/rspec/event_prof_sidekiq_fixture.rb +2 -3
- data/spec/integrations/fixtures/rspec/rspec_stamp_fixture_tmpl.rb +33 -0
- data/spec/integrations/rspec_stamp_spec.rb +53 -0
- data/spec/support/integration_helpers.rb +2 -2
- data/spec/test_prof/rspec_stamp/parser_spec.rb +58 -0
- data/spec/test_prof/rspec_stamp_spec.rb +281 -0
- metadata +10 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c9b58b9e9898a5b72c42700bfc0751c31639aefc
|
4
|
+
data.tar.gz: 4bf4799f92e06e8e9b3c000fe08659171cd2293d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
51
|
-
yield
|
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"
|
data/lib/test_prof/event_prof.rb
CHANGED
@@ -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
|
78
|
+
def initialize(event:, instrumenter:)
|
81
79
|
@event = event
|
82
80
|
|
83
81
|
log :info, "EventProf enabled (#{@event})"
|
@@ -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
|
data/lib/test_prof/version.rb
CHANGED
@@ -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:
|
81
|
-
expect(output).to match(%r{BatchJob \(./event_prof_sidekiq_fixture.rb:
|
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:
|
97
|
-
expect(output).to match(%r{BatchJob \(./event_prof_sidekiq_fixture.rb:
|
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
|
@@ -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.
|
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-
|
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
|