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 +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
|