Skip to content

Commit 8384bee

Browse files
authored
Merge 9fffaf8 into 0f4508b
2 parents 0f4508b + 9fffaf8 commit 8384bee

File tree

5 files changed

+72
-24
lines changed

5 files changed

+72
-24
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
## Unreleased ([changes](https://github.com/infertux/bashcov/compare/v2.0.0...master))
22

3-
* TBD
3+
* [BUGFIX] Correctly handle empty scripts by short-circuiting
4+
`FieldStream#each` if the reader stream is at end-of-file before
5+
the start-of-fields pattern is encountered (#41)
46

57
## v2.0.0, 2018-12-?? ([changes](https://github.com/infertux/bashcov/compare/v1.8.2...v2.0.0))
68

lib/bashcov/field_stream.rb

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -36,36 +36,48 @@ def each_field(delimiter)
3636
def each(delimiter, field_count, start_match)
3737
return enum_for(__method__, delimiter, field_count, start_match) unless block_given?
3838

39-
# The number of fields processed since passing the last start-of-fields
40-
# match
41-
seen_fields = 0
39+
chunked = each_field(delimiter).chunk(&chunk_matches(start_match))
4240

43-
fields = each_field(delimiter)
41+
yield_fields = lambda do |(_, chunk)|
42+
chunk.each { |e| yield e }
43+
(field_count - chunk.size).times { yield "" }
44+
end
45+
46+
# Skip junk that might appear before the first start-of-fields match
47+
begin
48+
n, chunk = chunked.next
49+
yield_fields.call([n, chunk]) unless n.zero?
50+
rescue StopIteration
51+
return
52+
end
4453

45-
# Close over +field_count+ and +seen_fields+ to yield empty strings to
46-
# the caller when we've already hit the next start-of-fields match
47-
yield_remaining = -> { (field_count - seen_fields).times { yield "" } }
54+
chunked.each(&yield_fields)
55+
end
4856

49-
# Advance until the first start-of-fields match
50-
loop { break if fields.next =~ start_match }
57+
private
5158

52-
fields.each do |field|
53-
# If the current field is the start-of-fields match...
54-
if field.match?(start_match)
55-
# Fill out any remaining (unparseable) fields with empty strings
56-
yield_remaining.call
59+
# @param [Regexp] start_match a +Regexp+ that, when matched against the
60+
# input stream, signifies the beginning of the next series of fields to
61+
# yield
62+
# @return [Proc] a unary +Proc+ that returns +nil+ if the argument mathes
63+
# the +start_match+ +Regexp+, and otherwise returns the number of
64+
# start-of-fields signifiers so far encountered.
65+
# @example
66+
# chunker = chunk_matches /<=>/
67+
# chunked = %w[foo fighters <=> bar none <=> baz luhrmann].chunk(&chunker)
68+
# chunked.to_a
69+
# #=> [[0, ["foo", "fighters"]], [1, ["bar", "none"]], [2, ["baz", "luhrmann"]]]
70+
def chunk_matches(start_match)
71+
i = 0
5772

58-
seen_fields = 0
59-
elsif seen_fields < field_count
60-
yield field
61-
seen_fields += 1
73+
lambda do |e|
74+
if e.match?(start_match)
75+
i += 1
76+
nil
77+
else
78+
i
6279
end
6380
end
64-
65-
# One last filling-out of empty fields if we're at the end of the stream
66-
yield_remaining.call
67-
68-
read.close unless read.closed?
6981
end
7082
end
7183
end

lib/bashcov/xtrace.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ def read
9797
parse_hit!(*hit)
9898
end
9999

100+
@read.close unless @read.closed?
101+
100102
@files
101103
end
102104

spec/bashcov/field_stream_spec.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,25 @@
5151
yield_successive_args(*expected)
5252
end
5353
end
54+
55+
context "when the stream is empty or already fully consumed" do
56+
let(:read) { StringIO.new }
57+
58+
it "short-circuits without yielding anything" do
59+
expect { |e| stream.each(delimiter, field_count, start_match, &e) }.not_to yield_control
60+
end
61+
end
62+
63+
context "when stream contains fields prior to the first start-of-fields match" do
64+
let(:before_start) { %w[some junk data] }
65+
let(:after_start) { %w[the good stuff] }
66+
let(:field_count) { 3 }
67+
let(:input) { (before_start + [start] + after_start).join(delimiter) }
68+
69+
it "discards them" do
70+
expect { |e| stream.each(delimiter, field_count, start_match, &e) }.to \
71+
yield_successive_args(*after_start)
72+
end
73+
end
5474
end
5575
end

spec/bashcov/runner_spec.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,18 @@
147147
end
148148
end
149149

150+
context "given an empty script" do
151+
include_context "temporary script", "empty_script" do
152+
let(:script_text) { "" }
153+
end
154+
155+
it "returns empty coverage results" do
156+
expect { tmprunner.run }.not_to raise_error
157+
expect(tmprunner.result).to include(tmpscript.path)
158+
expect(tmprunner.result[tmpscript.path]).to be_empty
159+
end
160+
end
161+
150162
context "given a version of Bash prior to 4.1", if: Bashcov::BASH_VERSION < "4.1" do
151163
include_context "temporary script", "no_stderr" do
152164
let(:stderr_output) { "AIEEE!" }

0 commit comments

Comments
 (0)