bashcov 1.2.1 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://secure.travis-ci.org/infertux/bashcov.png?branch=master)](https://travis-ci.org/infertux/bashcov)
|
4
|
+
[![Dependency Status](https://gemnasium.com/infertux/bashcov.png)](https://gemnasium.com/infertux/bashcov)
|
5
|
+
[![Code Climate](https://codeclimate.com/github/infertux/bashcov.png)](https://codeclimate.com/github/infertux/bashcov)
|
6
|
+
[![Coverage Status](https://coveralls.io/repos/infertux/bashcov/badge.png?branch=master)](https://coveralls.io/r/infertux/bashcov)
|
7
|
+
[![Gem Version](https://img.shields.io/gem/v/bashcov.svg)](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
|