bashcov 1.2.1 → 1.3.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 +4 -4
- data/CHANGELOG.md +16 -3
- data/Gemfile +2 -0
- data/Guardfile +2 -0
- data/LICENSE.txt +1 -1
- data/README.md +60 -5
- data/Rakefile +2 -0
- data/bashcov.gemspec +4 -3
- data/bin/bashcov +3 -2
- data/lib/bashcov/bash_info.rb +32 -0
- data/lib/bashcov/errors.rb +16 -0
- data/lib/bashcov/field_stream.rb +77 -0
- data/lib/bashcov/lexer.rb +13 -7
- data/lib/bashcov/line.rb +4 -2
- data/lib/bashcov/runner.rb +96 -21
- data/lib/bashcov/version.rb +4 -1
- data/lib/bashcov/xtrace.rb +150 -35
- data/lib/bashcov.rb +64 -31
- metadata +9 -58
- data/.gitignore +0 -19
- data/.rspec +0 -4
- data/.rubocop.yml +0 -27
- data/.travis.yml +0 -21
- data/spec/bashcov/lexer_spec.rb +0 -11
- data/spec/bashcov/runner_spec.rb +0 -98
- data/spec/bashcov_spec.rb +0 -82
- data/spec/spec_helper.rb +0 -25
- data/spec/support/common.rb +0 -7
- data/spec/support/test_app.rb +0 -35
- data/spec/test_app/.simplecov +0 -2
- data/spec/test_app/README.md +0 -1
- data/spec/test_app/never_called.sh +0 -4
- data/spec/test_app/scripts/README.md +0 -1
- data/spec/test_app/scripts/case.sh +0 -15
- data/spec/test_app/scripts/delete.sh +0 -9
- data/spec/test_app/scripts/executable +0 -4
- data/spec/test_app/scripts/exit_non_zero.sh +0 -3
- data/spec/test_app/scripts/function.sh +0 -20
- data/spec/test_app/scripts/long_line.sh +0 -7
- data/spec/test_app/scripts/multiline.sh +0 -24
- data/spec/test_app/scripts/nested/simple.sh +0 -13
- data/spec/test_app/scripts/one_liner.sh +0 -9
- data/spec/test_app/scripts/simple.sh +0 -13
- data/spec/test_app/scripts/source.sh +0 -6
- data/spec/test_app/scripts/sourced.txt +0 -4
- data/spec/test_app/scripts/unicode.sh +0 -4
- data/spec/test_app/test_suite.sh +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f2fc246b18fc2cbae42869d11c95270f5a5343b4
|
4
|
+
data.tar.gz: a82eae685fd50447bc27e5fe73eff1ff48513f1d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1e417c499432768996d30fac933516aa2baa52ac4087eec92d0a36c74b8a8df74d55331a3548e4c8e1f3cef9c80e65c6f2deac5d7d9d3629fedf518a92dff85e
|
7
|
+
data.tar.gz: e2f657b4097abcc4973ba66d81ec26e00ef29c881d761f0b02f3b5c1f467d3a6d2c0a11075c4c62115859aacfff9ff78e340043cf51ff0d337f2fa77d0d2fcf9
|
data/CHANGELOG.md
CHANGED
@@ -1,7 +1,20 @@
|
|
1
|
-
## Unreleased ([changes](https://github.com/infertux/bashcov/compare/v1.
|
1
|
+
## Unreleased ([changes](https://github.com/infertux/bashcov/compare/v1.3.0...master))
|
2
2
|
|
3
3
|
* TBD
|
4
4
|
|
5
|
+
## v1.3.0, 2016-02-10 ([changes](https://github.com/infertux/bashcov/compare/v1.2.1...v1.3.0))
|
6
|
+
|
7
|
+
* [FEATURE] Upgrade SimpleCov dependency to 0.11
|
8
|
+
* [FEATURE] Add support for Ruby 2.3 and drop 1.9
|
9
|
+
* [FEATURE] Add ability to pass `--bash-path` and `--root` as arguments
|
10
|
+
* [FEATURE] Add basic support for Bash versions prior to 4.1 (no `BASH_XTRACEFD`)
|
11
|
+
* [FEATURE] Handle `pushd` & `popd` commands
|
12
|
+
* [BUGFIX] Fix potential bug with long paths under Bash 4.2 as it truncates `PS4` to 128 characters
|
13
|
+
* [BUGFIX] Fail gracefully if a Bash script unsets `LINENO`
|
14
|
+
* [BUGFIX] Refactor parser to not use subshells in `PS4` as it causes erroneous extra hits as well as being slow (classes `FieldStream` & `Xtrace`)
|
15
|
+
Big kudos to @BaxterStockman for his awesome work on PR #16
|
16
|
+
See https://github.com/infertux/bashcov/#some-gory-details
|
17
|
+
|
5
18
|
## v1.2.1, 2015-05-05 ([changes](https://github.com/infertux/bashcov/compare/v1.2.0...v1.2.1))
|
6
19
|
|
7
20
|
* [BUGFIX] Preserve original exit status when exiting Bashcov
|
@@ -18,8 +31,8 @@
|
|
18
31
|
|
19
32
|
## v1.0.1, 2013-03-21 ([changes](https://github.com/infertux/bashcov/compare/v1.0.0...v1.0.1))
|
20
33
|
|
21
|
-
* [BUGFIX] Allow to add SimpleCov filters
|
22
|
-
* [BUGFIX] Lines containing only `elif` should be ignored
|
34
|
+
* [BUGFIX] Allow to add SimpleCov filters
|
35
|
+
* [BUGFIX] Lines containing only `elif` should be ignored
|
23
36
|
|
24
37
|
## v1.0.0, 2013-03-16 ([changes](https://github.com/infertux/bashcov/compare/v0.0.9...v1.0.0))
|
25
38
|
|
data/Gemfile
CHANGED
data/Guardfile
CHANGED
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,10 @@
|
|
1
|
-
# Bashcov
|
1
|
+
# Bashcov
|
2
|
+
|
3
|
+
[](https://travis-ci.org/infertux/bashcov)
|
4
|
+
[](https://gemnasium.com/infertux/bashcov)
|
5
|
+
[](https://codeclimate.com/github/infertux/bashcov)
|
6
|
+
[](https://coveralls.io/r/infertux/bashcov)
|
7
|
+
[](https://rubygems.org/gems/bashcov)
|
2
8
|
|
3
9
|
**Code coverage for Bash**
|
4
10
|
|
@@ -20,11 +26,12 @@
|
|
20
26
|
[Dependencies]: https://gemnasium.com/infertux/bashcov "Bashcov dependencies on Gemnasium"
|
21
27
|
[Bashcov]: https://github.com/infertux/bashcov
|
22
28
|
[SimpleCov]: https://github.com/colszowka/simplecov "Bashcov is backed by SimpleCov to generate awesome coverage report"
|
29
|
+
[Test app demo]: https://infertux.github.com/bashcov/test_app/ "Coverage for the bundled test application"
|
23
30
|
|
24
31
|
You should check out these coverage examples - it's worth a thousand words:
|
25
32
|
|
26
|
-
- [Test app demo]
|
27
|
-
- [RVM demo](
|
33
|
+
- [Test app demo]
|
34
|
+
- [RVM demo](https://infertux.github.com/bashcov/rvm/ "Coverage for RVM")
|
28
35
|
|
29
36
|
## Installation
|
30
37
|
|
@@ -48,8 +55,56 @@ Open `./coverage/index.html` to browse the coverage report.
|
|
48
55
|
|
49
56
|
### SimpleCov integration
|
50
57
|
|
51
|
-
You can take great advantage of [SimpleCov] by adding a `.simplecov` file in
|
52
|
-
|
58
|
+
You can take great advantage of [SimpleCov] by adding a `.simplecov` file in
|
59
|
+
your project's root (like [this](https://github.com/infertux/bashcov/blob/master/spec/test_app/.simplecov)).
|
60
|
+
See [SimpleCov README](https://github.com/colszowka/simplecov#readme) for more
|
61
|
+
information.
|
62
|
+
|
63
|
+
### Some gory details
|
64
|
+
|
65
|
+
Figuring out where an executing Bash script lives in the file system can be
|
66
|
+
surprisingly difficult. Bash offers a fair amount of [introspection into its
|
67
|
+
internals](https://www.gnu.org/software/bash/manual/html_node/Bash-Variables.html),
|
68
|
+
but the location of the current script has to be inferred from the limited
|
69
|
+
information available through `BASH_SOURCE`, `PWD`, and `OLDPWD` (and
|
70
|
+
potentially `DIRSTACK` if you are using `pushd`/`popd`). For this purpose,
|
71
|
+
Bashcov puts Bash in debug mode and sets up a `PS4` that expands the values of
|
72
|
+
these variables, reading them on each command that Bash executes. But, given
|
73
|
+
that:
|
74
|
+
|
75
|
+
* `BASH_SOURCE` is only an absolute path if the script was invoked using an
|
76
|
+
absolute path,
|
77
|
+
* the builtins `cd`, `pushd`, and `popd` alter `PWD` and `OLDPWD`, and
|
78
|
+
* none of these variables are read-only and can therefore be `unset` or
|
79
|
+
otherwise altered,
|
80
|
+
|
81
|
+
it can be easy to lose track of where we are.
|
82
|
+
|
83
|
+
_"Wait a minute, what about `pwd`, `readlink`, and so on?"_ That would be great,
|
84
|
+
except that subshells executed as part of expanding the `PS4` can cause Bash to
|
85
|
+
report [extra executions](https://github.com/infertux/bashcov/commit/4130874e30a05b7ab6ea66fb96a19acaa973c178)
|
86
|
+
for [certain lines](https://github.com/infertux/bashcov/pull/16). Also,
|
87
|
+
subshells are slow as the `PS4` is expanded on each and every command when Bash
|
88
|
+
is in debug mode.
|
89
|
+
|
90
|
+
To deal with these limitations, Bashcov uses the expedient of maintaining two
|
91
|
+
stacks that track changes to `PWD` and `OLDPWD`. To determine the full path to
|
92
|
+
the executing script, Bashcov iterates in reverse over the `PWD` stack, testing
|
93
|
+
for the first `$PWD/$BASH_SOURCE` combination that refers to an existing file.
|
94
|
+
This heuristic is susceptible to false positives -- under certain combinations
|
95
|
+
of directory structure, script invocation paths, and working directory changes,
|
96
|
+
it may yield a path that doesn't refer to the currently-running script.
|
97
|
+
However, it performs well under the various working directory changes performed
|
98
|
+
in the [test app demo] and avoids the spurious extra hits caused by using
|
99
|
+
subshells in the `PS4`.
|
100
|
+
|
101
|
+
One final note on innards: Bash 4.3 fixed a bug in which `PS4` expansion is
|
102
|
+
truncated to a maximum of 128 characters. On platforms whose Bash version
|
103
|
+
suffers from this bug, Bashcov uses the ASCII record separator character to
|
104
|
+
delimit the `PS4` fields, whereas it uses a long random string on Bash 4.3 and
|
105
|
+
above. When the field delimiter appears in the path of a script under test or
|
106
|
+
in a command the script executes, Bashcov won't correctly parse the `PS4` and
|
107
|
+
will abort early with incomplete coverage results.
|
53
108
|
|
54
109
|
## Contributing
|
55
110
|
|
data/Rakefile
CHANGED
data/bashcov.gemspec
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
lib = File.expand_path("../lib", __FILE__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
5
|
require "bashcov/version"
|
@@ -13,12 +14,12 @@ Gem::Specification.new do |gem|
|
|
13
14
|
gem.homepage = "https://github.com/infertux/bashcov"
|
14
15
|
gem.license = "MIT"
|
15
16
|
|
16
|
-
gem.files = `git ls-files`.split(
|
17
|
+
gem.files = `git ls-files -z`.split("\x0").reject { |f| f.start_with?(".") || f.match(%r{\A(test|spec|features)/}) }
|
17
18
|
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
18
19
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
20
|
gem.require_paths = ["lib"]
|
20
21
|
|
21
|
-
gem.add_dependency "simplecov", "~> 0.
|
22
|
+
gem.add_dependency "simplecov", "~> 0.11"
|
22
23
|
|
23
24
|
gem.add_development_dependency "rake"
|
24
25
|
gem.add_development_dependency "rspec", "~> 3"
|
data/bin/bashcov
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
lib = File.expand_path("../../lib", __FILE__)
|
4
5
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
@@ -7,13 +8,13 @@ require "bashcov"
|
|
7
8
|
|
8
9
|
Bashcov.parse_options! ARGV
|
9
10
|
|
10
|
-
runner = Bashcov::Runner.new Bashcov.
|
11
|
+
runner = Bashcov::Runner.new Bashcov.command
|
11
12
|
status = runner.run
|
12
13
|
coverage = runner.result
|
13
14
|
|
14
15
|
require "simplecov"
|
15
16
|
|
16
|
-
SimpleCov.command_name Bashcov.
|
17
|
+
SimpleCov.command_name Bashcov.fullname
|
17
18
|
SimpleCov.root Bashcov.root_directory
|
18
19
|
SimpleCov::Result.new(coverage).format!
|
19
20
|
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bashcov
|
4
|
+
# Module exposing information concerning the installed Bash version
|
5
|
+
# @note methods do not cache results because +bash_path+ can change at
|
6
|
+
# runtime
|
7
|
+
# @note receiver is expected to implement +bash_path+
|
8
|
+
module BashInfo
|
9
|
+
# @return [Array<String>] An array representing the components of
|
10
|
+
# +BASH_VERSINFO+
|
11
|
+
def bash_versinfo
|
12
|
+
`#{bash_path} -c 'echo "${BASH_VERSINFO[@]}"'`.chomp.split
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [Boolean] Whether Bash supports +BASH_XTRACEFD+
|
16
|
+
def bash_xtracefd?
|
17
|
+
bash_versinfo[0..1].join.to_i >= 41
|
18
|
+
end
|
19
|
+
|
20
|
+
# @param [Integer] length the number of bytes to test; default 128
|
21
|
+
# @return [Boolean] whether Bash supports a +PS4+ of at least a given
|
22
|
+
# number of bytes
|
23
|
+
# @see https://tiswww.case.edu/php/chet/bash/CHANGES
|
24
|
+
# @note Item +i.+ under the +bash-4.2-release+ to +bash-4.3-alpha+ change
|
25
|
+
# list notes that version 4.2 truncates +PS4+ if it is greater than 128
|
26
|
+
# bytes.
|
27
|
+
def truncated_ps4?(length = 128)
|
28
|
+
ps4 = SecureRandom.base64(length)
|
29
|
+
!`PS4=#{ps4} #{bash_path} 2>&1 1>&- -xc 'echo hello'`.start_with?(ps4)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bashcov
|
4
|
+
# Signals an error parsing Bash's debugging output.
|
5
|
+
class XtraceError < ::RuntimeError
|
6
|
+
# Will contain the coverages parsed prior to the error
|
7
|
+
attr_reader :files
|
8
|
+
|
9
|
+
# @param [#to_s] message An error message
|
10
|
+
# @param [Hash] files A hash containing coverage information
|
11
|
+
def initialize(message, files)
|
12
|
+
@files = files
|
13
|
+
super(message)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bashcov
|
4
|
+
# Classes for streaming token-delimited fields
|
5
|
+
class FieldStream
|
6
|
+
attr_accessor :read
|
7
|
+
|
8
|
+
# @param [IO] read an IO object opened for reading
|
9
|
+
def initialize(read = nil)
|
10
|
+
@read = read
|
11
|
+
end
|
12
|
+
|
13
|
+
# A convenience wrapper around +each_line(delimiter)+ that also does
|
14
|
+
# +chomp(delimiter)+ on the yielded line.
|
15
|
+
# @param [String, nil] delimiter the field separator for the stream
|
16
|
+
# @return [void]
|
17
|
+
# @yieldparam [String] field each +chomp+ed line
|
18
|
+
def each_field(delimiter)
|
19
|
+
return enum_for(__method__, delimiter) unless block_given?
|
20
|
+
|
21
|
+
read.each_line(delimiter) do |line|
|
22
|
+
yield line.chomp(delimiter)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Yields fields extracted from a input stream
|
27
|
+
# @param [String, nil] delimiter the field separator
|
28
|
+
# @param [Integer] field_count the number of fields to extract
|
29
|
+
# @param [Regexp] start_match a +Regexp+ that, when matched against the
|
30
|
+
# input stream, signifies the beginning of the next series of fields to
|
31
|
+
# yield
|
32
|
+
# @yieldparam [String] field each field extracted from the stream. If
|
33
|
+
# +start_match+ is matched with fewer than +field_count+ fields yielded
|
34
|
+
# since the last match, yields empty strings until +field_count+ is
|
35
|
+
# reached.
|
36
|
+
def each(delimiter, field_count, start_match)
|
37
|
+
unless block_given?
|
38
|
+
return enum_for(__method__, delimiter, field_count, start_match)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Whether the current field is the start-of-fields match
|
42
|
+
matched_start = nil
|
43
|
+
|
44
|
+
# The number of fields processed since passing the last start-of-fields
|
45
|
+
# match
|
46
|
+
seen_fields = 0
|
47
|
+
|
48
|
+
fields = each_field(delimiter)
|
49
|
+
|
50
|
+
# Close over +field_count+ and +seen_fields+ to yield empty strings to
|
51
|
+
# the caller when we've already hit the next start-of-fields match
|
52
|
+
yield_remaining = -> { (field_count - seen_fields).times { yield "" } }
|
53
|
+
|
54
|
+
# Advance until the first start-of-fields match
|
55
|
+
loop { break if fields.next =~ start_match }
|
56
|
+
|
57
|
+
fields.each do |field|
|
58
|
+
# If the current field is the start-of-fields match...
|
59
|
+
if field =~ start_match
|
60
|
+
# Fill out any remaining (unparseable) fields with empty strings
|
61
|
+
yield_remaining.call
|
62
|
+
|
63
|
+
matched_start = nil
|
64
|
+
seen_fields = 0
|
65
|
+
elsif seen_fields < field_count
|
66
|
+
yield field
|
67
|
+
seen_fields += 1
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# One last filling-out of empty fields if we're at the end of the stream
|
72
|
+
yield_remaining.call
|
73
|
+
|
74
|
+
read.close unless read.closed?
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/lib/bashcov/lexer.rb
CHANGED
@@ -1,24 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bashcov/line"
|
4
|
+
|
1
5
|
module Bashcov
|
2
6
|
# Simple lexer which analyzes Bash files in order to get information for
|
3
7
|
# coverage
|
4
8
|
class Lexer
|
5
9
|
# Lines starting with one of these tokens are irrelevant for coverage
|
6
|
-
IGNORE_START_WITH = %w(# function)
|
10
|
+
IGNORE_START_WITH = %w(# function).freeze
|
7
11
|
|
8
12
|
# Lines ending with one of these tokens are irrelevant for coverage
|
9
|
-
IGNORE_END_WITH = %w|(
|
13
|
+
IGNORE_END_WITH = %w|(|.freeze
|
10
14
|
|
11
15
|
# Lines containing only one of these keywords are irrelevant for coverage
|
12
|
-
IGNORE_IS = %w(esac if then else elif fi while do done { } ;;)
|
16
|
+
IGNORE_IS = %w(esac if then else elif fi while do done { } ;;).freeze
|
13
17
|
|
14
18
|
# @param [String] filename File to analyze
|
15
19
|
# @param [Hash] coverage Coverage with executed lines marked
|
16
20
|
# @raise [ArgumentError] if the given +filename+ is invalid.
|
17
21
|
def initialize(filename, coverage)
|
18
|
-
@filename =
|
22
|
+
@filename = filename
|
19
23
|
@coverage = coverage
|
20
24
|
|
21
|
-
|
25
|
+
unless File.file?(@filename)
|
26
|
+
raise ArgumentError, "#{@filename} is not a file"
|
27
|
+
end
|
22
28
|
end
|
23
29
|
|
24
30
|
# Yields uncovered relevant lines.
|
@@ -27,7 +33,7 @@ module Bashcov
|
|
27
33
|
def uncovered_relevant_lines
|
28
34
|
lineno = 0
|
29
35
|
File.open(@filename, "rb").each_line do |line|
|
30
|
-
if @coverage[lineno] == Bashcov::Line::IGNORED &&
|
36
|
+
if @coverage[lineno] == Bashcov::Line::IGNORED && relevant?(line)
|
31
37
|
yield lineno
|
32
38
|
end
|
33
39
|
lineno += 1
|
@@ -36,7 +42,7 @@ module Bashcov
|
|
36
42
|
|
37
43
|
private
|
38
44
|
|
39
|
-
def
|
45
|
+
def relevant?(line)
|
40
46
|
line.strip!
|
41
47
|
|
42
48
|
!line.empty? and
|
data/lib/bashcov/line.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Bashcov
|
2
4
|
# {Line} represents a line of code.
|
3
5
|
module Line
|
4
6
|
# Uncovered line
|
5
|
-
# @see http://
|
7
|
+
# @see http://ruby-doc.org/stdlib-2.3.0/libdoc/coverage/rdoc/Coverage.html
|
6
8
|
UNCOVERED = 0
|
7
9
|
|
8
10
|
# Ignored line
|
9
|
-
# @see http://
|
11
|
+
# @see http://ruby-doc.org/stdlib-2.3.0/libdoc/coverage/rdoc/Coverage.html
|
10
12
|
IGNORED = nil
|
11
13
|
end
|
12
14
|
end
|
data/lib/bashcov/runner.rb
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bashcov/errors"
|
4
|
+
require "bashcov/field_stream"
|
5
|
+
require "bashcov/lexer"
|
6
|
+
require "bashcov/xtrace"
|
7
|
+
|
1
8
|
module Bashcov
|
2
9
|
# Runs a given command with xtrace enabled then computes code coverage.
|
3
10
|
class Runner
|
@@ -10,21 +17,44 @@ module Bashcov
|
|
10
17
|
# @note Binds Bashcov +stdin+ to the program being executed.
|
11
18
|
# @return [Process::Status] Status of the executed command
|
12
19
|
def run
|
13
|
-
|
20
|
+
# Clear out previous run
|
21
|
+
@result = nil
|
22
|
+
|
23
|
+
field_stream = FieldStream.new
|
24
|
+
@xtrace = Xtrace.new(field_stream)
|
14
25
|
|
15
|
-
@xtrace = Xtrace.new
|
16
26
|
fd = @xtrace.file_descriptor
|
17
|
-
|
18
|
-
options
|
19
|
-
|
27
|
+
env = { "PS4" => Xtrace.ps4 }
|
28
|
+
options = { in: :in }
|
29
|
+
|
30
|
+
if Bashcov.options.mute
|
31
|
+
options[:out] = "/dev/null"
|
32
|
+
options[:err] = "/dev/null"
|
33
|
+
end
|
34
|
+
|
35
|
+
run_xtrace(fd, env, options) do
|
36
|
+
command_pid = Process.spawn env, *@command, options # spawn the command
|
20
37
|
|
21
|
-
|
22
|
-
|
38
|
+
begin
|
39
|
+
# start processing the xtrace output
|
40
|
+
xtrace_thread = Thread.new { @xtrace.read }
|
23
41
|
|
24
|
-
|
25
|
-
@xtrace.close
|
42
|
+
Process.wait command_pid
|
26
43
|
|
27
|
-
|
44
|
+
@xtrace.close
|
45
|
+
|
46
|
+
@coverage = xtrace_thread.value # wait for the thread to return
|
47
|
+
rescue XtraceError => e
|
48
|
+
write_warning <<-WARNING
|
49
|
+
encountered an error parsing Bash's output (error was:
|
50
|
+
#{e.message}). This can occur if your script or its path contains
|
51
|
+
the sequence #{Xtrace.delimiter.inspect}, or if your script unsets
|
52
|
+
LINENO. Aborting early; coverage report will be incomplete.
|
53
|
+
WARNING
|
54
|
+
|
55
|
+
@coverage = e.files
|
56
|
+
end
|
57
|
+
end
|
28
58
|
|
29
59
|
$?
|
30
60
|
end
|
@@ -37,48 +67,93 @@ module Bashcov
|
|
37
67
|
expunge_invalid_files!
|
38
68
|
mark_relevant_lines!
|
39
69
|
|
40
|
-
|
70
|
+
convert_coverage
|
41
71
|
end
|
42
72
|
end
|
43
73
|
|
44
74
|
private
|
45
75
|
|
76
|
+
def write_warning(message)
|
77
|
+
warn format "%s: warning: %s", Bashcov.program_name,
|
78
|
+
message.gsub(/^\s+/, "").lines.map(&:chomp).join(" ")
|
79
|
+
end
|
80
|
+
|
81
|
+
def run_xtrace(fd, env, options)
|
82
|
+
# Older versions of Bash (< 4.1) don't have the BASH_XTRACEFD variable
|
83
|
+
if Bashcov.bash_xtracefd?
|
84
|
+
options[fd] = fd # bind FDs to the child process
|
85
|
+
|
86
|
+
env["BASH_XTRACEFD"] = fd.to_s
|
87
|
+
else
|
88
|
+
# Send subprocess standard error to @xtrace.file_descriptor
|
89
|
+
options[:err] = fd
|
90
|
+
|
91
|
+
# Don't bother issuing warning if we're silencing output anyway
|
92
|
+
unless Bashcov.mute
|
93
|
+
write_warning <<-WARNING
|
94
|
+
you are using a version of Bash that does not support
|
95
|
+
BASH_XTRACEFD. All xtrace output will print to standard error, and
|
96
|
+
your script's output on standard error will not be printed to the
|
97
|
+
console.
|
98
|
+
WARNING
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
inject_xtrace_flag! do
|
103
|
+
yield
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
46
107
|
# @note +SHELLOPTS+ must be exported so we use Ruby's {ENV} variable
|
47
|
-
# @
|
108
|
+
# @yield [void] adds "xtrace" to +SHELLOPTS+ and then runs the provided
|
109
|
+
# block
|
110
|
+
# @return [Object, ...] the value returned by the calling block
|
48
111
|
def inject_xtrace_flag!
|
49
|
-
|
112
|
+
existing_flags_s = ENV["SHELLOPTS"]
|
113
|
+
existing_flags = (existing_flags_s || "").split(":")
|
50
114
|
ENV["SHELLOPTS"] = (existing_flags | ["xtrace"]).join(":")
|
115
|
+
|
116
|
+
yield
|
117
|
+
ensure
|
118
|
+
ENV["SHELLOPTS"] = existing_flags_s
|
51
119
|
end
|
52
120
|
|
53
121
|
# Add files which have not been executed at all (i.e. with no coverage)
|
54
122
|
# @return [void]
|
55
123
|
def find_bash_files!
|
56
|
-
return if Bashcov.
|
124
|
+
return if Bashcov.skip_uncovered
|
57
125
|
|
58
|
-
|
59
|
-
@coverage[
|
126
|
+
Pathname.glob("#{Bashcov.root_directory}/**/*.sh").each do |filename|
|
127
|
+
@coverage[filename] = [] unless @coverage.include?(filename)
|
60
128
|
end
|
61
129
|
end
|
62
130
|
|
63
131
|
# @return [void]
|
64
132
|
def expunge_invalid_files!
|
65
|
-
@coverage.each_key do |
|
66
|
-
next if
|
133
|
+
@coverage.each_key do |filename|
|
134
|
+
next if filename.file?
|
67
135
|
|
68
|
-
@coverage.delete
|
69
|
-
|
136
|
+
@coverage.delete filename
|
137
|
+
write_warning <<-WARNING
|
138
|
+
#{filename} was executed but has been deleted since then - it won't
|
139
|
+
be reported in coverage.
|
140
|
+
WARNING
|
70
141
|
end
|
71
142
|
end
|
72
143
|
|
73
144
|
# @see Lexer
|
74
145
|
# @return [void]
|
75
146
|
def mark_relevant_lines!
|
76
|
-
@coverage.
|
147
|
+
@coverage.each_pair do |filename, coverage|
|
77
148
|
lexer = Lexer.new(filename, coverage)
|
78
149
|
lexer.uncovered_relevant_lines do |lineno|
|
79
150
|
@coverage[filename][lineno] = Bashcov::Line::UNCOVERED
|
80
151
|
end
|
81
152
|
end
|
82
153
|
end
|
154
|
+
|
155
|
+
def convert_coverage
|
156
|
+
Hash[@coverage.map { |filename, coverage| [filename.to_s, coverage] }]
|
157
|
+
end
|
83
158
|
end
|
84
159
|
end
|