code_driven_development 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 94d84107c92129b3d475303b3d6153d5b459462f
4
+ data.tar.gz: 626c026e88e042aa506242509963069cbe0c3f5c
5
+ SHA512:
6
+ metadata.gz: 16a3e47fd78cbba40485de2795e6610a206b33636cd4a8b0b08f498526070b205851bfabec6e293c13fe137aea8cb3446be8a573e7f42e6601bc2b7dd1f914a9
7
+ data.tar.gz: 6cb4a8d957fb9bb496f0de6c822aec06b8433ffae10340fdfd16ed679ec833cf8038f09995a38256a91540f75f05197ad5cc45a9e6e3521a040ad6e65ff4d5f6
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format d
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in tester.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Dan Finnie
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,79 @@
1
+ # CodeDrivenDevelopment
2
+
3
+ Ever see a test and think, "wow, this test cares a lot about the exact abstract
4
+ syntax tree of the code?" CDD aims to generate tests like that. Here's some example input:
5
+
6
+ ``` ruby
7
+ class Bunker < ActiveRecord::Base
8
+ validate :password, secure: true
9
+ validate :location, :protected => true
10
+
11
+ def alert_staff
12
+ Staff.alert_all
13
+ end
14
+
15
+ def alert_president
16
+ SecretService.stand_down
17
+ President.alert
18
+ end
19
+ end
20
+ ```
21
+
22
+ And this is what CDD thinks of it:
23
+
24
+ ``` ruby
25
+ describe Bunker do
26
+ it { should validate_secure_of :password }
27
+ it { should validate_protected_of :location }
28
+
29
+ describe "#alert_staff" do
30
+ before do
31
+ allow(Staff).to receive :alert_all
32
+ described_class.new.alert_staff
33
+ end
34
+ it "calls Staff.alert_all" do
35
+ expect(Staff).to have_received :alert_all
36
+ end
37
+ end
38
+
39
+ describe "#alert_president" do
40
+ before do
41
+ allow(SecretService).to receive :stand_down
42
+ allow(President).to receive :alert
43
+ described_class.new.alert_president
44
+ end
45
+ it "calls SecretService.stand_down" do
46
+ expect(SecretService).to have_received :stand_down
47
+ end
48
+ it "calls President.alert" do
49
+ expect(President).to have_received :alert
50
+ end
51
+ end
52
+ end
53
+ ```
54
+
55
+ ## Installation
56
+
57
+ Add this line to your application's Gemfile:
58
+
59
+ gem 'code_driven_development'
60
+
61
+ And then execute:
62
+
63
+ $ bundle
64
+
65
+ Or install it yourself as:
66
+
67
+ $ gem install code_driven_development
68
+
69
+ ## Usage
70
+
71
+ TODO: Write usage instructions here
72
+
73
+ ## Contributing
74
+
75
+ 1. Fork it
76
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
77
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
78
+ 4. Push to the branch (`git push origin my-new-feature`)
79
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/cdd ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'code_driven_development'
4
+ puts CodeDrivenDevelopment::CodeDrivenDevelopment.new(ARGF.read).test_code.strip
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'code_driven_development/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "code_driven_development"
8
+ spec.version = CodeDrivenDevelopment::VERSION
9
+ spec.authors = ["Dan Finnie"]
10
+ spec.email = ["dan@danfinnie.com"]
11
+ spec.description = %q{TOddddd: Write a gem description}
12
+ spec.summary = %q{TOddddd: Write a gem summary}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "ruby_parser"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.3"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "rspec"
26
+ end
@@ -0,0 +1,31 @@
1
+ module CodeDrivenDevelopment
2
+ class CodeDrivenDevelopment
3
+ def initialize(implementation)
4
+ @implementation = implementation
5
+ end
6
+
7
+ def test_code
8
+ test_context = TestComponent::BlankSlate.new
9
+ ruleset.test_for(parse_tree, test_context)
10
+ test_context.indented_output
11
+ end
12
+
13
+ private
14
+
15
+ attr_reader :implementation
16
+
17
+ def parse_tree
18
+ RubyParser.new.parse(implementation)
19
+ end
20
+
21
+ def ruleset
22
+ Rule::Set.new(
23
+ Rule::Class,
24
+ Rule::Validation,
25
+ Rule::InstanceMethod,
26
+ Rule::MethodCall,
27
+ default: Rule::Default
28
+ )
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,24 @@
1
+ module CodeDrivenDevelopment
2
+ class IndentedOutput
3
+ def initialize
4
+ @nesting = 0
5
+ @output = ""
6
+ end
7
+
8
+ def <<(str)
9
+ @output << current_indentation << str << "\n"
10
+ end
11
+
12
+ def indented
13
+ @nesting += 1
14
+ yield
15
+ @nesting -= 1
16
+ end
17
+
18
+ private
19
+
20
+ def current_indentation
21
+ "\t" * @nesting
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,29 @@
1
+ module CodeDrivenDevelopment
2
+ module Rule
3
+ class AbstractRule
4
+ def initialize(code, ruleset, test_context)
5
+ @code = code
6
+ @ruleset = ruleset
7
+ @test_context = test_context
8
+ end
9
+
10
+ def capable?
11
+ raise NotImplementedError.new("#{self.class} doesn't yet implement capable?!")
12
+ end
13
+
14
+ def test
15
+ raise NotImplementedError.new("#{self.class} doesn't yet implement test!")
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :code, :ruleset, :test_context
21
+
22
+ def recurse body, child_test_context
23
+ body.each do |line|
24
+ ruleset.test_for(line, child_test_context)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,25 @@
1
+ module CodeDrivenDevelopment
2
+ module Rule
3
+ class Class < AbstractRule
4
+ def capable?
5
+ code.sexp_type == :class
6
+ end
7
+
8
+ def test
9
+ new_context = TestComponent::Context.new(class_name)
10
+ recurse(class_body, new_context)
11
+ test_context << new_context
12
+ end
13
+
14
+ private
15
+
16
+ def class_body
17
+ code[3..-1]
18
+ end
19
+
20
+ def class_name
21
+ code[1]
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,8 @@
1
+ module CodeDrivenDevelopment
2
+ module Rule
3
+ class Default < AbstractRule
4
+ def test
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,28 @@
1
+ module CodeDrivenDevelopment
2
+ module Rule
3
+ class InstanceMethod < AbstractRule
4
+ def capable?
5
+ code.sexp_type == :defn
6
+ end
7
+
8
+ def test
9
+ new_context = TestComponent::Context.new(%Q("##{method_name}"))
10
+ recurse(method_body, new_context)
11
+ test_context << new_context
12
+
13
+ # Do this last so that the method invocation is the last line in the before.
14
+ new_context.befores << "described_class.new.#{method_name}"
15
+ end
16
+
17
+ private
18
+
19
+ def method_name
20
+ code[1]
21
+ end
22
+
23
+ def method_body
24
+ code[3..-1]
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,26 @@
1
+ module CodeDrivenDevelopment
2
+ module Rule
3
+ class MethodCall < AbstractRule
4
+ def capable?
5
+ code.sexp_type == :call &&
6
+ method_name != :validate
7
+ end
8
+
9
+ def test
10
+ test_context.befores << "allow(#{receiver}).to receive :#{method_name}"
11
+ body = ["expect(#{receiver}).to have_received :#{method_name}"]
12
+ test_context << TestComponent::Test.new("calls #{receiver}.#{method_name}", body)
13
+ end
14
+
15
+ private
16
+
17
+ def receiver
18
+ code[1].value
19
+ end
20
+
21
+ def method_name
22
+ code[2]
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,29 @@
1
+ module CodeDrivenDevelopment
2
+ module Rule
3
+ class Set
4
+ def initialize(*rules, default: nil)
5
+ @rules = rules
6
+ @default_rule = default
7
+ end
8
+
9
+ def test_for(code, test_context)
10
+ capable_rules = rules.map do |klass|
11
+ klass.new(code, self, test_context)
12
+ end.select(&:capable?)
13
+
14
+ case capable_rules.count
15
+ when 0
16
+ default_rule.new(code, self, test_context).test
17
+ when 1
18
+ capable_rules.first.test
19
+ else
20
+ raise "Multiple rules matched #{code.inspect}:\n\t" + capable_rules.map(&:class).map(&:to_s).join("\n\t")
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :rules, :default_rule
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,33 @@
1
+ module CodeDrivenDevelopment
2
+ module Rule
3
+ class Validation < AbstractRule
4
+ def capable?
5
+ code.sexp_type == :call &&
6
+ code.sexp_body[0].nil? &&
7
+ code.sexp_body[1] == :validate
8
+ end
9
+
10
+ def test
11
+ test_context << TestComponent::OneLineTest.new("should validate_#{type}_of :#{field}")
12
+ end
13
+
14
+ private
15
+
16
+ def field
17
+ extract_symbol(code[3])
18
+ end
19
+
20
+ def type
21
+ extract_symbol(extract_hash_key(code[-1]))
22
+ end
23
+
24
+ def extract_symbol(literal)
25
+ literal[1]
26
+ end
27
+
28
+ def extract_hash_key(hash)
29
+ hash[1]
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,19 @@
1
+ module CodeDrivenDevelopment
2
+ module TestComponent
3
+ class BlankSlate
4
+ def initialize
5
+ @children = []
6
+ end
7
+
8
+ def indented_output(io = IndentedOutput.new)
9
+ @children.map do |child|
10
+ child.indented_output(io)
11
+ end.join
12
+ end
13
+
14
+ def << child
15
+ @children << child
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,32 @@
1
+ module CodeDrivenDevelopment
2
+ module TestComponent
3
+ class Context
4
+ def initialize(description = nil, befores = [], tests = [])
5
+ @description, @befores, @tests = description, befores, tests
6
+ end
7
+
8
+ attr_reader :tests, :befores
9
+ attr_accessor :description
10
+
11
+ def << child
12
+ @tests << child
13
+ end
14
+
15
+ def indented_output io
16
+ io << ""
17
+ io << "describe #@description do"
18
+ io.indented do
19
+ if befores.any?
20
+ io << "before do"
21
+ io.indented do
22
+ befores.each { |before| io << before }
23
+ end
24
+ io << "end"
25
+ end
26
+ tests.each { |test| test.indented_output(io) }
27
+ end
28
+ io << "end"
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,15 @@
1
+ module CodeDrivenDevelopment
2
+ module TestComponent
3
+ class OneLineTest
4
+ def initialize(body = "")
5
+ @body = body
6
+ end
7
+
8
+ attr_accessor :body
9
+
10
+ def indented_output(io)
11
+ io << "it { #@body }"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,19 @@
1
+ module CodeDrivenDevelopment
2
+ module TestComponent
3
+ class Test
4
+ def initialize(description = "", body = "")
5
+ @description, @body = description, body
6
+ end
7
+
8
+ attr_accessor :description, :body
9
+
10
+ def indented_output io
11
+ io << "it \"#@description\" do"
12
+ io.indented do
13
+ body.each { |line| io << line }
14
+ end
15
+ io << "end"
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,3 @@
1
+ module CodeDrivenDevelopment
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,18 @@
1
+ require 'ruby_parser'
2
+
3
+ require "code_driven_development/version"
4
+ require "code_driven_development/code_driven_development"
5
+ require "code_driven_development/indented_output"
6
+
7
+ require "code_driven_development/test_component/test"
8
+ require "code_driven_development/test_component/one_line_test"
9
+ require "code_driven_development/test_component/context"
10
+ require "code_driven_development/test_component/blank_slate"
11
+
12
+ require "code_driven_development/rule/abstract_rule"
13
+ require "code_driven_development/rule/set"
14
+ require "code_driven_development/rule/method_call"
15
+ require "code_driven_development/rule/default"
16
+ require "code_driven_development/rule/class"
17
+ require "code_driven_development/rule/validation"
18
+ require "code_driven_development/rule/instance_method"
@@ -0,0 +1,13 @@
1
+ class Bunker < ActiveRecord::Base
2
+ validate :password, secure: true
3
+ validate :location, :protected => true
4
+
5
+ def alert_staff
6
+ Staff.alert_all
7
+ end
8
+
9
+ def alert_president
10
+ SecretService.stand_down
11
+ President.alert
12
+ end
13
+ end
data/spec/my_spec.rb ADDED
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+
3
+ describe CodeDrivenDevelopment::CodeDrivenDevelopment do
4
+ before do
5
+ implementation = <<-EOT
6
+ class Lolcat < ActiveRecord::Base
7
+ validate :cuteness, presence: true
8
+
9
+ def i_call_things
10
+ CentralBureaucracy.file_report
11
+ end
12
+ end
13
+ EOT
14
+
15
+ @test = CodeDrivenDevelopment::CodeDrivenDevelopment.new(implementation).test_code
16
+ end
17
+
18
+ it "puts the model name in the describe description" do
19
+ expect(@test).to match /^describe Lolcat do/
20
+ end
21
+
22
+ it "includes shoulda-matchers for simple validations" do
23
+ expect(@test).to match /^\tit.*should.*validate_presence_of.*:cuteness/
24
+ end
25
+
26
+ it "stubs out method calls" do
27
+ expect(@test).to have_consecutive_lines_matching [
28
+ /^\tdescribe.*"#i_call_things"/,
29
+ /^\t\tbefore/,
30
+ /^\t\t\tallow.CentralBureaucracy..to.*receive.*:file_report/,
31
+ /^\t\t\tdescribed_class.new.i_call_things/,
32
+ /^\t\tit.*calls Central.*do/,
33
+ /^\t\t\texpect.CentralBureaucracy..to.*have_received.*:file_report/
34
+ ]
35
+ end
36
+ end
@@ -0,0 +1,13 @@
1
+ require_relative '../lib/code_driven_development'
2
+ require_relative 'support/have_consecutive_lines_matching_matcher'
3
+
4
+ RSpec.configure do |config|
5
+ config.expect_with :rspec do |c|
6
+ c.syntax = :expect
7
+ end
8
+
9
+ config.treat_symbols_as_metadata_keys_with_true_values = true
10
+ config.run_all_when_everything_filtered = true
11
+ config.filter_run :focus
12
+ config.order = 'random'
13
+ end
@@ -0,0 +1,29 @@
1
+ RSpec::Matchers.define :have_consecutive_lines_matching do |*regexen|
2
+ def compute_match(string, regexen)
3
+ if regexen.empty?
4
+ return [true]
5
+ end
6
+
7
+ regex = regexen.shift
8
+ m = string.match(regex)
9
+ if m
10
+ remaining = m.post_match
11
+ newline_i = remaining.index("\n")+1
12
+ remaining = remaining[newline_i..-1]
13
+ compute_match(remaining, regexen)
14
+ else
15
+ return [false, regex, string]
16
+ end
17
+ end
18
+
19
+ match do |actual|
20
+ compute_match(actual, regexen.flatten).shift
21
+ end
22
+
23
+ failure_message_for_should do |actual|
24
+ _, failed_regex, failed_string = compute_match(actual, regexen.flatten)
25
+ "expected #{failed_regex.inspect} to match #{failed_string.inspect}"
26
+ end
27
+
28
+ diffable
29
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: code_driven_development
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Dan Finnie
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-02-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ruby_parser
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: 'TOddddd: Write a gem description'
70
+ email:
71
+ - dan@danfinnie.com
72
+ executables:
73
+ - cdd
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - .gitignore
78
+ - .rspec
79
+ - Gemfile
80
+ - LICENSE.txt
81
+ - README.md
82
+ - Rakefile
83
+ - bin/cdd
84
+ - code-driven-development.gemspec
85
+ - lib/code_driven_development.rb
86
+ - lib/code_driven_development/code_driven_development.rb
87
+ - lib/code_driven_development/indented_output.rb
88
+ - lib/code_driven_development/rule/abstract_rule.rb
89
+ - lib/code_driven_development/rule/class.rb
90
+ - lib/code_driven_development/rule/default.rb
91
+ - lib/code_driven_development/rule/instance_method.rb
92
+ - lib/code_driven_development/rule/method_call.rb
93
+ - lib/code_driven_development/rule/set.rb
94
+ - lib/code_driven_development/rule/validation.rb
95
+ - lib/code_driven_development/test_component/blank_slate.rb
96
+ - lib/code_driven_development/test_component/context.rb
97
+ - lib/code_driven_development/test_component/one_line_test.rb
98
+ - lib/code_driven_development/test_component/test.rb
99
+ - lib/code_driven_development/version.rb
100
+ - samples/validations.rb
101
+ - spec/my_spec.rb
102
+ - spec/spec_helper.rb
103
+ - spec/support/have_consecutive_lines_matching_matcher.rb
104
+ homepage: ''
105
+ licenses:
106
+ - MIT
107
+ metadata: {}
108
+ post_install_message:
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - '>='
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - '>='
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ requirements: []
123
+ rubyforge_project:
124
+ rubygems_version: 2.0.0
125
+ signing_key:
126
+ specification_version: 4
127
+ summary: 'TOddddd: Write a gem summary'
128
+ test_files:
129
+ - spec/my_spec.rb
130
+ - spec/spec_helper.rb
131
+ - spec/support/have_consecutive_lines_matching_matcher.rb