super_diff 0.1.0
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 +7 -0
- data/README.md +174 -0
- data/lib/super_diff/csi/color_helper.rb +52 -0
- data/lib/super_diff/csi/eight_bit_color.rb +131 -0
- data/lib/super_diff/csi/eight_bit_sequence.rb +27 -0
- data/lib/super_diff/csi/four_bit_color.rb +80 -0
- data/lib/super_diff/csi/four_bit_sequence.rb +24 -0
- data/lib/super_diff/csi/reset_sequence.rb +9 -0
- data/lib/super_diff/csi/sequence.rb +22 -0
- data/lib/super_diff/csi/twenty_four_bit_color.rb +41 -0
- data/lib/super_diff/csi/twenty_four_bit_sequence.rb +27 -0
- data/lib/super_diff/csi.rb +29 -0
- data/lib/super_diff/diff_formatter.rb +37 -0
- data/lib/super_diff/diff_formatters/array.rb +21 -0
- data/lib/super_diff/diff_formatters/base.rb +37 -0
- data/lib/super_diff/diff_formatters/collection.rb +107 -0
- data/lib/super_diff/diff_formatters/hash.rb +34 -0
- data/lib/super_diff/diff_formatters/multi_line_string.rb +31 -0
- data/lib/super_diff/diff_formatters/object.rb +27 -0
- data/lib/super_diff/diff_formatters.rb +5 -0
- data/lib/super_diff/differ.rb +48 -0
- data/lib/super_diff/differs/array.rb +24 -0
- data/lib/super_diff/differs/base.rb +42 -0
- data/lib/super_diff/differs/empty.rb +13 -0
- data/lib/super_diff/differs/hash.rb +24 -0
- data/lib/super_diff/differs/multi_line_string.rb +27 -0
- data/lib/super_diff/differs/object.rb +68 -0
- data/lib/super_diff/differs.rb +5 -0
- data/lib/super_diff/equality_matcher.rb +45 -0
- data/lib/super_diff/equality_matchers/array.rb +44 -0
- data/lib/super_diff/equality_matchers/base.rb +42 -0
- data/lib/super_diff/equality_matchers/hash.rb +44 -0
- data/lib/super_diff/equality_matchers/multi_line_string.rb +44 -0
- data/lib/super_diff/equality_matchers/object.rb +18 -0
- data/lib/super_diff/equality_matchers/single_line_string.rb +28 -0
- data/lib/super_diff/equality_matchers.rb +5 -0
- data/lib/super_diff/errors.rb +20 -0
- data/lib/super_diff/helpers.rb +96 -0
- data/lib/super_diff/operation_sequences/array.rb +14 -0
- data/lib/super_diff/operation_sequences/base.rb +11 -0
- data/lib/super_diff/operation_sequences/hash.rb +14 -0
- data/lib/super_diff/operation_sequences/object.rb +14 -0
- data/lib/super_diff/operational_sequencer.rb +43 -0
- data/lib/super_diff/operational_sequencers/array.rb +127 -0
- data/lib/super_diff/operational_sequencers/base.rb +97 -0
- data/lib/super_diff/operational_sequencers/hash.rb +82 -0
- data/lib/super_diff/operational_sequencers/multi_line_string.rb +85 -0
- data/lib/super_diff/operational_sequencers/object.rb +96 -0
- data/lib/super_diff/operational_sequencers.rb +5 -0
- data/lib/super_diff/operations/binary_operation.rb +47 -0
- data/lib/super_diff/operations/unary_operation.rb +25 -0
- data/lib/super_diff/rspec/differ.rb +30 -0
- data/lib/super_diff/rspec/monkey_patches.rb +122 -0
- data/lib/super_diff/rspec.rb +19 -0
- data/lib/super_diff/value_inspection.rb +11 -0
- data/lib/super_diff/version.rb +3 -0
- data/lib/super_diff.rb +50 -0
- data/spec/examples.txt +46 -0
- data/spec/integration/rspec_spec.rb +261 -0
- data/spec/spec_helper.rb +44 -0
- data/spec/support/color_helper.rb +49 -0
- data/spec/support/command_runner.rb +279 -0
- data/spec/support/integration/matchers/produce_output_when_run_matcher.rb +76 -0
- data/spec/support/person.rb +23 -0
- data/spec/support/person_diff_formatter.rb +15 -0
- data/spec/support/person_operation_sequence.rb +14 -0
- data/spec/support/person_operational_sequencer.rb +19 -0
- data/spec/unit/equality_matcher_spec.rb +1233 -0
- data/super_diff.gemspec +23 -0
- metadata +153 -0
@@ -0,0 +1,261 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe "Integration with RSpec", type: :integration do
|
4
|
+
context "comparing two different integers" do
|
5
|
+
it "produces the correct output" do
|
6
|
+
test = <<~TEST
|
7
|
+
expect(1).to eq(42)
|
8
|
+
TEST
|
9
|
+
|
10
|
+
expected_output = <<~OUTPUT
|
11
|
+
expected: 42
|
12
|
+
got: 1
|
13
|
+
OUTPUT
|
14
|
+
|
15
|
+
expect(test).to produce_output_when_run(expected_output)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "comparing two different symbols" do
|
20
|
+
it "produces the correct output" do
|
21
|
+
test = <<~TEST
|
22
|
+
expect(:bar).to eq(:foo)
|
23
|
+
TEST
|
24
|
+
|
25
|
+
expected_output = <<~OUTPUT
|
26
|
+
expected: :foo
|
27
|
+
got: :bar
|
28
|
+
OUTPUT
|
29
|
+
|
30
|
+
expect(test).to produce_output_when_run(expected_output)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context "comparing two different single-line strings" do
|
35
|
+
it "produces the correct output" do
|
36
|
+
test = <<~TEST
|
37
|
+
expect("Jennifer").to eq("Marty")
|
38
|
+
TEST
|
39
|
+
|
40
|
+
expected_output = <<~OUTPUT
|
41
|
+
expected: "Marty"
|
42
|
+
got: "Jennifer"
|
43
|
+
OUTPUT
|
44
|
+
|
45
|
+
expect(test).to produce_output_when_run(expected_output)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context "comparing two closely different multi-line strings" do
|
50
|
+
it "produces the correct output" do
|
51
|
+
test = <<~TEST
|
52
|
+
expected = "This is a line\nAnd that's a line\nAnd there's a line too"
|
53
|
+
actual = "This is a line\nSomething completely different\nAnd there's a line too"
|
54
|
+
expect(actual).to eq(expected)
|
55
|
+
TEST
|
56
|
+
|
57
|
+
expected_output = <<~OUTPUT
|
58
|
+
Diff:
|
59
|
+
|
60
|
+
#{
|
61
|
+
colored do
|
62
|
+
plain_line %( This is a line⏎)
|
63
|
+
red_line %(- And that's a line⏎)
|
64
|
+
green_line %(+ Something completely different⏎)
|
65
|
+
plain_line %( And there's a line too)
|
66
|
+
end
|
67
|
+
}
|
68
|
+
OUTPUT
|
69
|
+
|
70
|
+
expect(test).to produce_output_when_run(expected_output)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context "comparing two completely different multi-line strings" do
|
75
|
+
it "produces the correct output" do
|
76
|
+
test = <<~TEST
|
77
|
+
expected = "This is a line\nAnd that's a line\n"
|
78
|
+
actual = "Something completely different\nAnd something else too\n"
|
79
|
+
expect(actual).to eq(expected)
|
80
|
+
TEST
|
81
|
+
|
82
|
+
expected_output = <<~OUTPUT
|
83
|
+
Diff:
|
84
|
+
|
85
|
+
#{
|
86
|
+
colored do
|
87
|
+
red_line %(- This is a line⏎)
|
88
|
+
red_line %(- And that's a line⏎)
|
89
|
+
green_line %(+ Something completely different⏎)
|
90
|
+
green_line %(+ And something else too⏎)
|
91
|
+
end
|
92
|
+
}
|
93
|
+
OUTPUT
|
94
|
+
|
95
|
+
expect(test).to produce_output_when_run(expected_output)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context "comparing two arrays with other data structures inside" do
|
100
|
+
it "produces the correct output" do
|
101
|
+
test = <<~TEST
|
102
|
+
expected = [
|
103
|
+
[
|
104
|
+
:h1,
|
105
|
+
[:span, [:text, "Hello world"]],
|
106
|
+
{
|
107
|
+
class: "header",
|
108
|
+
data: {
|
109
|
+
"sticky" => true,
|
110
|
+
person: SuperDiff::Test::Person.new(name: "Marty")
|
111
|
+
}
|
112
|
+
}
|
113
|
+
]
|
114
|
+
]
|
115
|
+
actual = [
|
116
|
+
[
|
117
|
+
:h2,
|
118
|
+
[:span, [:text, "Goodbye world"]],
|
119
|
+
{
|
120
|
+
id: "hero",
|
121
|
+
class: "header",
|
122
|
+
data: {
|
123
|
+
"sticky" => false,
|
124
|
+
role: "deprecated",
|
125
|
+
person: SuperDiff::Test::Person.new(name: "Doc")
|
126
|
+
}
|
127
|
+
}
|
128
|
+
],
|
129
|
+
:br
|
130
|
+
]
|
131
|
+
expect(actual).to eq(expected)
|
132
|
+
TEST
|
133
|
+
|
134
|
+
expected_output = <<~OUTPUT
|
135
|
+
Diff:
|
136
|
+
|
137
|
+
#{
|
138
|
+
colored do
|
139
|
+
plain_line %( [)
|
140
|
+
plain_line %( [)
|
141
|
+
red_line %(- :h1,)
|
142
|
+
green_line %(+ :h2,)
|
143
|
+
plain_line %( [)
|
144
|
+
plain_line %( :span,)
|
145
|
+
plain_line %( [)
|
146
|
+
plain_line %( :text,)
|
147
|
+
red_line %(- "Hello world")
|
148
|
+
green_line %(+ "Goodbye world")
|
149
|
+
plain_line %( ])
|
150
|
+
plain_line %( ],)
|
151
|
+
plain_line %( {)
|
152
|
+
plain_line %( class: "header",)
|
153
|
+
plain_line %( data: {)
|
154
|
+
red_line %(- "sticky" => true,)
|
155
|
+
green_line %(+ "sticky" => false,)
|
156
|
+
plain_line %( person: #<SuperDiff::Test::Person {)
|
157
|
+
red_line %(- name: "Marty")
|
158
|
+
green_line %(+ name: "Doc")
|
159
|
+
plain_line %( }>,)
|
160
|
+
green_line %(+ role: "deprecated")
|
161
|
+
plain_line %( },)
|
162
|
+
green_line %(+ id: "hero")
|
163
|
+
plain_line %( })
|
164
|
+
plain_line %( ],)
|
165
|
+
green_line %(+ :br)
|
166
|
+
plain_line %( ])
|
167
|
+
end
|
168
|
+
}
|
169
|
+
OUTPUT
|
170
|
+
|
171
|
+
expect(test).to produce_output_when_run(expected_output)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
context "comparing two hashes with other data structures inside" do
|
176
|
+
it "produces the correct output" do
|
177
|
+
test = <<~TEST
|
178
|
+
expected = {
|
179
|
+
customer: {
|
180
|
+
person: SuperDiff::Test::Person.new(name: "Marty McFly"),
|
181
|
+
shipping_address: {
|
182
|
+
line_1: "123 Main St.",
|
183
|
+
city: "Hill Valley",
|
184
|
+
state: "CA",
|
185
|
+
zip: "90382"
|
186
|
+
}
|
187
|
+
},
|
188
|
+
items: [
|
189
|
+
{
|
190
|
+
name: "Fender Stratocaster",
|
191
|
+
cost: 100_000,
|
192
|
+
options: ["red", "blue", "green"]
|
193
|
+
},
|
194
|
+
{ name: "Chevy 4x4" }
|
195
|
+
]
|
196
|
+
}
|
197
|
+
actual = {
|
198
|
+
customer: {
|
199
|
+
person: SuperDiff::Test::Person.new(name: "Marty McFly, Jr."),
|
200
|
+
shipping_address: {
|
201
|
+
line_1: "456 Ponderosa Ct.",
|
202
|
+
city: "Hill Valley",
|
203
|
+
state: "CA",
|
204
|
+
zip: "90382"
|
205
|
+
}
|
206
|
+
},
|
207
|
+
items: [
|
208
|
+
{
|
209
|
+
name: "Fender Stratocaster",
|
210
|
+
cost: 100_000,
|
211
|
+
options: ["red", "blue", "green"]
|
212
|
+
},
|
213
|
+
{ name: "Mattel Hoverboard" }
|
214
|
+
]
|
215
|
+
}
|
216
|
+
expect(actual).to eq(expected)
|
217
|
+
TEST
|
218
|
+
|
219
|
+
expected_output = <<~OUTPUT
|
220
|
+
Diff:
|
221
|
+
|
222
|
+
#{
|
223
|
+
colored do
|
224
|
+
plain_line %( {)
|
225
|
+
plain_line %( customer: {)
|
226
|
+
plain_line %( person: #<SuperDiff::Test::Person {)
|
227
|
+
red_line %(- name: "Marty McFly")
|
228
|
+
green_line %(+ name: "Marty McFly, Jr.")
|
229
|
+
plain_line %( }>,)
|
230
|
+
plain_line %( shipping_address: {)
|
231
|
+
red_line %(- line_1: "123 Main St.",)
|
232
|
+
green_line %(+ line_1: "456 Ponderosa Ct.",)
|
233
|
+
plain_line %( city: "Hill Valley",)
|
234
|
+
plain_line %( state: "CA",)
|
235
|
+
plain_line %( zip: "90382")
|
236
|
+
plain_line %( })
|
237
|
+
plain_line %( },)
|
238
|
+
plain_line %( items: [)
|
239
|
+
plain_line %( {)
|
240
|
+
plain_line %( name: "Fender Stratocaster",)
|
241
|
+
plain_line %( cost: 100000,)
|
242
|
+
plain_line %( options: ["red", "blue", "green"])
|
243
|
+
plain_line %( },)
|
244
|
+
plain_line %( {)
|
245
|
+
red_line %(- name: "Chevy 4x4")
|
246
|
+
green_line %(+ name: "Mattel Hoverboard")
|
247
|
+
plain_line %( })
|
248
|
+
plain_line %( ])
|
249
|
+
plain_line %( })
|
250
|
+
end
|
251
|
+
}
|
252
|
+
OUTPUT
|
253
|
+
|
254
|
+
expect(test).to produce_output_when_run(expected_output)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
def colored(&block)
|
259
|
+
SuperDiff::Tests::Colorizer.call(&block).chomp
|
260
|
+
end
|
261
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require_relative "../lib/super_diff"
|
2
|
+
require_relative "../lib/super_diff/rspec"
|
3
|
+
|
4
|
+
begin
|
5
|
+
require "pry-byebug"
|
6
|
+
rescue LoadError
|
7
|
+
end
|
8
|
+
|
9
|
+
begin
|
10
|
+
require "pry-nav"
|
11
|
+
rescue LoadError
|
12
|
+
end
|
13
|
+
|
14
|
+
Dir.glob(File.expand_path("support/**/*.rb", __dir__)).each do |file|
|
15
|
+
require file
|
16
|
+
end
|
17
|
+
|
18
|
+
RSpec.configure do |config|
|
19
|
+
config.include(SuperDiff::IntegrationTests, type: :integration)
|
20
|
+
|
21
|
+
config.expect_with :rspec do |expectations|
|
22
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
23
|
+
expectations.max_formatted_output_length = nil
|
24
|
+
end
|
25
|
+
|
26
|
+
config.mock_with :rspec do |mocks|
|
27
|
+
mocks.verify_partial_doubles = true
|
28
|
+
end
|
29
|
+
|
30
|
+
config.shared_context_metadata_behavior = :apply_to_host_groups
|
31
|
+
config.filter_run_when_matching :focus
|
32
|
+
config.example_status_persistence_file_path = "spec/examples.txt"
|
33
|
+
config.disable_monkey_patching!
|
34
|
+
config.warnings = true
|
35
|
+
|
36
|
+
if config.files_to_run.one?
|
37
|
+
config.default_formatter = "documentation"
|
38
|
+
end
|
39
|
+
|
40
|
+
config.order = :random
|
41
|
+
Kernel.srand config.seed
|
42
|
+
end
|
43
|
+
|
44
|
+
require "pp"
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module SuperDiff
|
2
|
+
module Tests
|
3
|
+
class Colorizer < BasicObject
|
4
|
+
def self.call(&block)
|
5
|
+
new.instance_eval(&block).to_s
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@string = ""
|
10
|
+
end
|
11
|
+
|
12
|
+
def method_missing(name, *args, &block)
|
13
|
+
match = name.to_s.match(/\A(.+)_line\Z/)
|
14
|
+
|
15
|
+
if match
|
16
|
+
real_name = match.captures[0]
|
17
|
+
|
18
|
+
if Csi::ColorHelper.respond_to?(real_name)
|
19
|
+
addition = Csi::ColorHelper.public_send(real_name, *args)
|
20
|
+
|
21
|
+
if match
|
22
|
+
addition << "\n"
|
23
|
+
end
|
24
|
+
|
25
|
+
@string << addition
|
26
|
+
else
|
27
|
+
super
|
28
|
+
end
|
29
|
+
else
|
30
|
+
super
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def respond_to_missing?(name, include_private = false)
|
35
|
+
match = name.to_s.match(/\A(.+)_line\Z/)
|
36
|
+
|
37
|
+
if match
|
38
|
+
Csi::ColorHelper.respond_to?(match.captures[0]) || super
|
39
|
+
else
|
40
|
+
super
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_s
|
45
|
+
@string.strip
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,279 @@
|
|
1
|
+
require "childprocess"
|
2
|
+
|
3
|
+
require "English"
|
4
|
+
require "shellwords"
|
5
|
+
require "timeout"
|
6
|
+
|
7
|
+
class CommandRunner
|
8
|
+
module OutputHelpers
|
9
|
+
def self.divider(title = "")
|
10
|
+
total_length = 72
|
11
|
+
start_length = 3
|
12
|
+
|
13
|
+
string = ""
|
14
|
+
string << ("-" * start_length)
|
15
|
+
string << title
|
16
|
+
string << "-" * (total_length - start_length - title.length)
|
17
|
+
string << "\n"
|
18
|
+
string
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class CommandFailedError < StandardError
|
23
|
+
def self.create(command:, exit_status:, output:, message: nil)
|
24
|
+
allocate.tap do |error|
|
25
|
+
error.command = command
|
26
|
+
error.exit_status = exit_status
|
27
|
+
error.output = output
|
28
|
+
error.__send__(:initialize, message)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
attr_accessor :command, :exit_status, :output
|
33
|
+
|
34
|
+
def initialize(message = nil)
|
35
|
+
super(message || build_message)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def build_message
|
41
|
+
message = <<-MESSAGE
|
42
|
+
Command #{command.inspect} failed, exiting with status #{exit_status}.
|
43
|
+
MESSAGE
|
44
|
+
|
45
|
+
if output
|
46
|
+
message << <<-MESSAGE
|
47
|
+
Output:
|
48
|
+
#{OutputHelpers.divider("START") + output + OutputHelpers.divider("END")}
|
49
|
+
MESSAGE
|
50
|
+
end
|
51
|
+
|
52
|
+
message
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class CommandTimedOutError < StandardError
|
57
|
+
def self.create(command:, timeout:, output:, message: nil)
|
58
|
+
allocate.tap do |error|
|
59
|
+
error.command = command
|
60
|
+
error.timeout = timeout
|
61
|
+
error.output = output
|
62
|
+
error.__send__(:initialize, message)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
attr_accessor :command, :timeout, :output
|
67
|
+
|
68
|
+
def initialize(message = nil)
|
69
|
+
super(message || build_message)
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def build_message
|
75
|
+
message = <<-MESSAGE
|
76
|
+
Command #{formatted_command.inspect} timed out after #{timeout} seconds.
|
77
|
+
MESSAGE
|
78
|
+
|
79
|
+
if output
|
80
|
+
message << <<-MESSAGE
|
81
|
+
Output:
|
82
|
+
#{OutputHelpers.divider("START") + output + OutputHelpers.divider("END")}
|
83
|
+
MESSAGE
|
84
|
+
end
|
85
|
+
|
86
|
+
message
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.run(*args)
|
91
|
+
new(*args).tap do |runner|
|
92
|
+
yield runner if block_given?
|
93
|
+
runner.run
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.run!(*args)
|
98
|
+
run(*args) do |runner|
|
99
|
+
runner.run_successfully = true
|
100
|
+
yield runner if block_given?
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
attr_reader :status, :options, :env
|
105
|
+
attr_accessor :run_quickly, :run_successfully, :retries,
|
106
|
+
:timeout
|
107
|
+
|
108
|
+
def initialize(*args)
|
109
|
+
@reader, @writer = IO.pipe
|
110
|
+
options = (args.last.is_a?(Hash) ? args.pop : {})
|
111
|
+
@args = args
|
112
|
+
@options = options.merge(
|
113
|
+
err: [:child, :out],
|
114
|
+
out: @writer,
|
115
|
+
)
|
116
|
+
@env = extract_env_from(@options)
|
117
|
+
|
118
|
+
@process = ChildProcess.build(*command)
|
119
|
+
@process.io.stdout = @process.io.stderr = @writer
|
120
|
+
|
121
|
+
@wrapper = -> (block) { block.call }
|
122
|
+
self.directory = Dir.pwd
|
123
|
+
@run_quickly = false
|
124
|
+
@run_successfully = false
|
125
|
+
@retries = 1
|
126
|
+
@num_times_run = 0
|
127
|
+
@timeout = 10
|
128
|
+
end
|
129
|
+
|
130
|
+
def around_command(&block)
|
131
|
+
@wrapper = block
|
132
|
+
end
|
133
|
+
|
134
|
+
def directory
|
135
|
+
@options[:chdir]
|
136
|
+
end
|
137
|
+
|
138
|
+
def directory=(directory)
|
139
|
+
@options[:chdir] = (directory || Dir.pwd).to_s
|
140
|
+
end
|
141
|
+
|
142
|
+
def formatted_command
|
143
|
+
[formatted_env, Shellwords.join(command)].
|
144
|
+
reject(&:empty?).
|
145
|
+
join(" ")
|
146
|
+
end
|
147
|
+
|
148
|
+
def run
|
149
|
+
possibly_running_quickly do
|
150
|
+
run_with_debugging
|
151
|
+
|
152
|
+
if run_successfully && !success?
|
153
|
+
fail!
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
self
|
158
|
+
end
|
159
|
+
|
160
|
+
def stop
|
161
|
+
if !writer.closed?
|
162
|
+
writer.close
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def output
|
167
|
+
@_output ||= begin
|
168
|
+
stop
|
169
|
+
reader.read
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def elided_output
|
174
|
+
lines = output.split(/\n/)
|
175
|
+
new_lines = lines[0..4]
|
176
|
+
|
177
|
+
if lines.size > 10
|
178
|
+
new_lines << "(...#{lines.size - 10} more lines...)"
|
179
|
+
end
|
180
|
+
|
181
|
+
new_lines << lines[-5..-1]
|
182
|
+
new_lines.join("\n")
|
183
|
+
end
|
184
|
+
|
185
|
+
def success?
|
186
|
+
status.success?
|
187
|
+
end
|
188
|
+
|
189
|
+
def exit_status
|
190
|
+
status.exitstatus
|
191
|
+
end
|
192
|
+
|
193
|
+
def fail!
|
194
|
+
raise CommandFailedError.create(
|
195
|
+
command: formatted_command,
|
196
|
+
exit_status: exit_status,
|
197
|
+
output: output,
|
198
|
+
)
|
199
|
+
end
|
200
|
+
|
201
|
+
def has_output?(expected_output)
|
202
|
+
if expected_output.is_a?(Regexp)
|
203
|
+
output =~ expected_output
|
204
|
+
else
|
205
|
+
output.include?(expected_output)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
protected
|
210
|
+
|
211
|
+
attr_reader :args, :reader, :writer, :wrapper, :process
|
212
|
+
|
213
|
+
private
|
214
|
+
|
215
|
+
def extract_env_from(options)
|
216
|
+
options.delete(:env) { {} }.reduce({}) do |hash, (key, value)|
|
217
|
+
hash.merge(key.to_s => value)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def command
|
222
|
+
args.flatten.flat_map { |word| Shellwords.split(word) }
|
223
|
+
end
|
224
|
+
|
225
|
+
def formatted_env
|
226
|
+
env.map { |key, value| "#{key}=#{value.inspect}" }.join(" ")
|
227
|
+
end
|
228
|
+
|
229
|
+
def run_freely
|
230
|
+
process.start
|
231
|
+
process.wait
|
232
|
+
end
|
233
|
+
|
234
|
+
def run_with_wrapper
|
235
|
+
wrapper.call(method(:run_freely))
|
236
|
+
end
|
237
|
+
|
238
|
+
def run_with_debugging
|
239
|
+
debug { "\n\e[33mChanging to directory:\e[0m #{directory}" }
|
240
|
+
debug { "\e[32mRunning command:\e[0m #{formatted_command}" }
|
241
|
+
|
242
|
+
run_with_wrapper
|
243
|
+
|
244
|
+
debug do
|
245
|
+
"\n" +
|
246
|
+
OutputHelpers.divider("START") +
|
247
|
+
output +
|
248
|
+
OutputHelpers.divider("END")
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def possibly_running_quickly(&block)
|
253
|
+
if run_quickly
|
254
|
+
begin
|
255
|
+
Timeout.timeout(timeout, &block)
|
256
|
+
rescue Timeout::Error
|
257
|
+
stop
|
258
|
+
|
259
|
+
raise CommandTimedOutError.create(
|
260
|
+
command: formatted_command,
|
261
|
+
timeout: timeout,
|
262
|
+
output: output,
|
263
|
+
)
|
264
|
+
end
|
265
|
+
else
|
266
|
+
yield
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def debugging_enabled?
|
271
|
+
ENV["DEBUG_COMMANDS"] == "1"
|
272
|
+
end
|
273
|
+
|
274
|
+
def debug
|
275
|
+
if debugging_enabled?
|
276
|
+
puts yield
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module SuperDiff
|
2
|
+
module IntegrationTests
|
3
|
+
def produce_output_when_run(output)
|
4
|
+
ProduceOutputWhenRunMatcher.new(output)
|
5
|
+
end
|
6
|
+
|
7
|
+
class ProduceOutputWhenRunMatcher
|
8
|
+
PROJECT_DIRECTORY = Pathname.new("../../../../../").expand_path(__FILE__)
|
9
|
+
TEMP_DIRECTORY = PROJECT_DIRECTORY.join("tmp")
|
10
|
+
|
11
|
+
def initialize(expected_output)
|
12
|
+
@expected_output =
|
13
|
+
expected_output.
|
14
|
+
strip.
|
15
|
+
split(/(\n+)/).
|
16
|
+
map { |line| line =~ /\n+/ ? line : (" " * 7) + line }.
|
17
|
+
join
|
18
|
+
end
|
19
|
+
|
20
|
+
def matches?(test)
|
21
|
+
@test = test.strip
|
22
|
+
TEMP_DIRECTORY.mkpath
|
23
|
+
actual_output.include?(expected_output)
|
24
|
+
end
|
25
|
+
|
26
|
+
def failure_message
|
27
|
+
"Expected test to produce output, but it did not.\n\n" +
|
28
|
+
"Expected output:\n\n" +
|
29
|
+
CommandRunner::OutputHelpers.divider("START") +
|
30
|
+
expected_output + "\n" +
|
31
|
+
CommandRunner::OutputHelpers.divider("END") +
|
32
|
+
"\n" +
|
33
|
+
"Actual output:\n\n" +
|
34
|
+
CommandRunner::OutputHelpers.divider("START") +
|
35
|
+
actual_output + "\n" +
|
36
|
+
CommandRunner::OutputHelpers.divider("END")
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
attr_reader :expected_output, :test
|
42
|
+
|
43
|
+
def actual_output
|
44
|
+
@_actual_output ||=
|
45
|
+
CommandRunner.run("rspec", tempfile.to_s).output.strip
|
46
|
+
end
|
47
|
+
|
48
|
+
def tempfile
|
49
|
+
@_tempfile =
|
50
|
+
TEMP_DIRECTORY.join("acceptance_spec.rb").
|
51
|
+
tap { |tempfile| tempfile.write(program) }
|
52
|
+
end
|
53
|
+
|
54
|
+
def program
|
55
|
+
<<~PROGRAM
|
56
|
+
require "#{PROJECT_DIRECTORY.join("lib/super_diff/rspec.rb")}"
|
57
|
+
require "#{PROJECT_DIRECTORY.join("spec/support/person.rb")}"
|
58
|
+
require "#{PROJECT_DIRECTORY.join("spec/support/person_diff_formatter.rb")}"
|
59
|
+
require "#{PROJECT_DIRECTORY.join("spec/support/person_operation_sequence.rb")}"
|
60
|
+
require "#{PROJECT_DIRECTORY.join("spec/support/person_operational_sequencer.rb")}"
|
61
|
+
|
62
|
+
SuperDiff::RSpec.configure do |config|
|
63
|
+
config.extra_operational_sequencer_classes << SuperDiff::Test::PersonOperationalSequencer
|
64
|
+
config.extra_diff_formatter_classes << SuperDiff::Test::PersonDiffFormatter
|
65
|
+
end
|
66
|
+
|
67
|
+
RSpec.describe "test" do
|
68
|
+
it "passes" do
|
69
|
+
#{test}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
PROGRAM
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|