bashcov 0.0.9 → 1.0.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 +12 -0
- data/CONTRIBUTING.md +9 -0
- data/Guardfile +1 -1
- data/LICENSE.txt +1 -1
- data/README.md +38 -28
- data/bashcov.gemspec +1 -1
- data/bin/bashcov +1 -1
- data/lib/bashcov.rb +19 -30
- data/lib/bashcov/lexer.rb +28 -28
- data/lib/bashcov/line.rb +1 -1
- data/lib/bashcov/runner.rb +42 -43
- data/lib/bashcov/version.rb +2 -2
- data/lib/bashcov/xtrace.rb +39 -48
- data/spec/bashcov/lexer_spec.rb +1 -23
- data/spec/bashcov/runner_spec.rb +35 -44
- data/spec/spec_helper.rb +1 -1
- data/spec/support/test_app.rb +18 -27
- data/spec/test_app/scripts/function.sh +6 -0
- data/spec/test_app/scripts/unicode.sh +4 -0
- metadata +10 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d8360ca3ab8a2d06296fbd911a15045bf10257d1
|
4
|
+
data.tar.gz: 9de0f7c886d9ab871d6b17870788bce1925e2b30
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cae406036062359b26ddf208eb95dab86d9263aef1a1b04eb37c628300678281b83834183d6c037713a67a648ad9a90e2570ca045f5b75b39380995aeb6852b3
|
7
|
+
data.tar.gz: 18f0ba69db66d6c36b414c8dd0bd6710f5a3d82ec566b3acc9a5d4cdcff6a5bd806ae6f3a79317970869b15dea7ce1f1db2139762478cf002181d9683ddca41e
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
## Unreleased ([changes](https://github.com/infertux/bashcov/compare/v1.0.0...master))
|
2
|
+
|
3
|
+
* TBD
|
4
|
+
|
5
|
+
## v1.0.0, 2013-03-16 ([changes](https://github.com/infertux/bashcov/compare/v0.0.9...v1.0.0))
|
6
|
+
|
7
|
+
* First stable release. Enjoy!
|
8
|
+
|
9
|
+
## v0.0.1 to v0.0.9, 2012-12-08 to 2013-03-05 ([changes](https://github.com/infertux/bashcov/compare/v0.0.1...v0.0.9))
|
10
|
+
|
11
|
+
* Experimental pre-releases. You should avoid to use these versions.
|
12
|
+
|
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
# Making Contributions
|
2
|
+
|
3
|
+
If you want to contribute, please:
|
4
|
+
|
5
|
+
* Fork the project.
|
6
|
+
* Make your feature addition or bug fix in a new branch.
|
7
|
+
* Add tests for it. This is important so I don't break it in a future version unintentionally.
|
8
|
+
* Send me a pull request on Github.
|
9
|
+
|
data/Guardfile
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# More info at https://github.com/guard/guard#readme
|
2
2
|
|
3
|
-
guard 'rspec', :
|
3
|
+
guard 'rspec', cli: '--tag ~speed:slow' do
|
4
4
|
watch(%r{^spec/.+_spec\.rb$})
|
5
5
|
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
6
6
|
watch(%r{^lib/(.+)/(.+)\.rb$}) { |m| "spec/#{m[1]}/#{m[2]}_spec.rb" }
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -1,8 +1,27 @@
|
|
1
1
|
# Bashcov [](https://travis-ci.org/infertux/bashcov) [](https://gemnasium.com/infertux/bashcov) [](https://codeclimate.com/github/infertux/bashcov)
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
**Code coverage for Bash**
|
4
|
+
|
5
|
+
* [Source Code]
|
6
|
+
* [Bug Tracker]
|
7
|
+
* [API Documentation]
|
8
|
+
* [Changelog]
|
9
|
+
* [Rubygem]
|
10
|
+
* [Continuous Integration]
|
11
|
+
* [Dependencies]
|
12
|
+
* [SimpleCov]
|
13
|
+
|
14
|
+
[Source Code]: https://github.com/infertux/bashcov "Source Code on Github"
|
15
|
+
[Bug Tracker]: https://github.com/infertux/bashcov/issues "Bug Tracker on Github"
|
16
|
+
[API documentation]: http://rubydoc.info/gems/bashcov/frames "API Documentation on Rubydoc"
|
17
|
+
[Changelog]: https://github.com/infertux/bashcov/blob/master/CHANGELOG.md "Project Changelog"
|
18
|
+
[Rubygem]: https://rubygems.org/gems/bashcov "Bashcov on Rubygems"
|
19
|
+
[Continuous Integration]: https://travis-ci.org/infertux/bashcov "Bashcov on Travis-CI"
|
20
|
+
[Dependencies]: https://gemnasium.com/infertux/bashcov "Bashcov dependencies on Gemnasium"
|
21
|
+
[Bashcov]: https://github.com/infertux/bashcov
|
22
|
+
[SimpleCov]: https://github.com/colszowka/simplecov "Bashcov is backed by SimpleCov to generate awesome coverage report"
|
23
|
+
|
24
|
+
You should check out the **[demo](http://infertux.github.com/bashcov/test_app/)** -- it's worth a thousand words.
|
6
25
|
|
7
26
|
## Installation
|
8
27
|
|
@@ -10,40 +29,31 @@ Check out the **[demo](http://infertux.github.com/bashcov/test_app/)** - it's wo
|
|
10
29
|
|
11
30
|
## Usage
|
12
31
|
|
13
|
-
`bashcov
|
14
|
-
|
15
|
-
This will create a directory named `coverage/` containing HTML files.
|
16
|
-
|
17
|
-
## Rationale
|
18
|
-
|
19
|
-
I'm a big fan of both Ruby's _simplecov_ and Bash.
|
20
|
-
_bashcov_ is my dream to have in Bash what _simplecov_ is to Ruby :).
|
32
|
+
`bashcov --help` prints all available options.
|
33
|
+
Here are some examples:
|
21
34
|
|
22
|
-
|
35
|
+
bashcov ./script.sh
|
36
|
+
bashcov --skip-uncovered ./script.sh
|
37
|
+
bashcov -- ./script.sh --some --flags
|
38
|
+
bashcov --skip-uncovered -- ./script.sh --some --flags
|
23
39
|
|
24
|
-
|
40
|
+
`script.sh` can be a mere Bash script or typically your CI script.
|
41
|
+
Bashcov will keep track of all executed scripts.
|
25
42
|
|
26
|
-
|
27
|
-
|
43
|
+
Then it will create a directory named `./coverage/` containing nice HTML files.
|
44
|
+
Open `./coverage/index.html` to browse the coverage report.
|
28
45
|
|
29
|
-
|
46
|
+
### SimpleCov integration
|
30
47
|
|
31
|
-
|
48
|
+
You can take great advantage of [SimpleCov] by adding a `.simplecov` file in your project's root (like [this](https://github.com/infertux/bashcov/blob/master/spec/test_app/.simplecov)).
|
49
|
+
See [SimpleCov README](https://github.com/colszowka/simplecov#readme) for more information.
|
32
50
|
|
33
|
-
|
34
|
-
Unfortunately, Bash doesn't have such niceties but we can use the [xtrace feature](http://www.gnu.org/software/bash/manual/bashref.html#index-BASH_005fXTRACEFD-178) which prints every line executed using [PS4](http://www.gnu.org/software/bash/manual/bashref.html#index-PS4).
|
51
|
+
## Contributing
|
35
52
|
|
36
|
-
|
37
|
-
|
38
|
-
And of course, you can take the most of _simplecov_ by adding a `.simplecov` file in your project's root (like [this](https://github.com/infertux/bashcov/blob/master/spec/test_app/.simplecov)).
|
53
|
+
Bug reports and patches are most welcome.
|
54
|
+
See the [contribution guidelines](https://github.com/infertux/bashcov/blob/master/CONTRIBUTING.md).
|
39
55
|
|
40
56
|
## License
|
41
57
|
|
42
58
|
MIT
|
43
59
|
|
44
|
-
|
45
|
-
[bashcov]: https://github.com/infertux/bashcov
|
46
|
-
[simplecov]: https://github.com/colszowka/simplecov
|
47
|
-
[simplecov-html]: https://github.com/colszowka/simplecov-html
|
48
|
-
[shcov]: http://code.google.com/p/shcov/source/browse/trunk/scripts/shcov
|
49
|
-
|
data/bashcov.gemspec
CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |gem|
|
|
18
18
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
19
|
gem.require_paths = ["lib"]
|
20
20
|
|
21
|
-
gem.add_dependency 'simplecov'
|
21
|
+
gem.add_dependency 'simplecov', '~> 0.7.1'
|
22
22
|
|
23
23
|
gem.add_development_dependency 'rake'
|
24
24
|
gem.add_development_dependency 'rspec'
|
data/bin/bashcov
CHANGED
data/lib/bashcov.rb
CHANGED
@@ -10,9 +10,15 @@ require 'bashcov/xtrace'
|
|
10
10
|
# @note Keep it short!
|
11
11
|
module Bashcov
|
12
12
|
class << self
|
13
|
+
|
13
14
|
# @return [OpenStruct] Bashcov settings
|
14
15
|
attr_reader :options
|
15
16
|
|
17
|
+
# @return [String] The project's root directory inferred from the command
|
18
|
+
def root_directory
|
19
|
+
@root_directory ||= File.dirname(File.absolute_path(@options.command))
|
20
|
+
end
|
21
|
+
|
16
22
|
# Sets default options overriding any existing ones.
|
17
23
|
# @return [void]
|
18
24
|
def set_default_options!
|
@@ -35,51 +41,35 @@ module Bashcov
|
|
35
41
|
end
|
36
42
|
end
|
37
43
|
|
38
|
-
# @return [String]
|
39
|
-
def root_directory
|
40
|
-
Dir.getwd
|
41
|
-
end
|
42
|
-
|
43
|
-
# Program name including version for easy consistent output
|
44
|
-
# @return [String]
|
44
|
+
# @return [String] Program name including version for easy consistent output
|
45
45
|
def name
|
46
46
|
"bashcov v#{VERSION}"
|
47
47
|
end
|
48
48
|
|
49
|
-
# Helper to get a pre-filled coverage array for a given file
|
50
|
-
# @todo This is generic and should be moved in some helpers file.
|
51
|
-
# @api private
|
52
|
-
# @param [String] filename The file to cover.
|
53
|
-
# @param [nil, Integer] fill Value to fill the array with.
|
54
|
-
# @return [Array] An array of the size of the given file.
|
55
|
-
# @example
|
56
|
-
# coverage_array('file.rb') #=> [0, 0, 0] # assuming file.rb has 3 lines
|
57
|
-
def coverage_array(filename, fill = Line::UNCOVERED)
|
58
|
-
lines = File.readlines(filename).size
|
59
|
-
[fill] * lines
|
60
|
-
end
|
61
|
-
|
62
49
|
private
|
63
50
|
|
51
|
+
def help program_name
|
52
|
+
<<-HELP.gsub!(/^ +/, '').gsub!("\t", ' ' * 4)
|
53
|
+
Usage: #{program_name} [options] [--] <command> [options]
|
54
|
+
Examples:
|
55
|
+
\t#{program_name} ./script.sh
|
56
|
+
\t#{program_name} --skip-uncovered ./script.sh
|
57
|
+
\t#{program_name} -- ./script.sh --some --flags
|
58
|
+
\t#{program_name} --skip-uncovered -- ./script.sh --some --flags
|
59
|
+
HELP
|
60
|
+
end
|
61
|
+
|
64
62
|
def option_parser
|
65
63
|
OptionParser.new do |opts|
|
66
64
|
opts.program_name = 'bashcov'
|
67
65
|
opts.version = Bashcov::VERSION
|
68
|
-
opts.banner =
|
69
|
-
Usage: #{opts.program_name} [options] -- <command> [options]
|
70
|
-
Examples:
|
71
|
-
\t#{opts.program_name} ./script.sh
|
72
|
-
\t#{opts.program_name} --skip-uncovered ./script.sh
|
73
|
-
\t#{opts.program_name} -- ./script.sh --some --flags
|
74
|
-
\t#{opts.program_name} --skip-uncovered -- ./script.sh --some --flags
|
75
|
-
HELP
|
66
|
+
opts.banner = help opts.program_name
|
76
67
|
|
77
68
|
opts.separator "\nSpecific options:"
|
78
69
|
|
79
70
|
opts.on("-s", "--skip-uncovered", "Do not report uncovered files") do |s|
|
80
71
|
@options.skip_uncovered = s
|
81
72
|
end
|
82
|
-
|
83
73
|
opts.on("-m", "--mute", "Do not print script output") do |m|
|
84
74
|
@options.mute = m
|
85
75
|
end
|
@@ -89,7 +79,6 @@ module Bashcov
|
|
89
79
|
opts.on_tail("-h", "--help", "Show this message") do
|
90
80
|
abort(opts.help)
|
91
81
|
end
|
92
|
-
|
93
82
|
opts.on_tail("--version", "Show version") do
|
94
83
|
puts opts.ver
|
95
84
|
exit
|
data/lib/bashcov/lexer.rb
CHANGED
@@ -2,50 +2,50 @@ module Bashcov
|
|
2
2
|
# Simple lexer which analyzes Bash files in order to get information for
|
3
3
|
# coverage
|
4
4
|
class Lexer
|
5
|
+
# Lines starting with one of these tokens are irrelevant for coverage
|
6
|
+
IGNORE_START_WITH = %w|# function|
|
7
|
+
|
8
|
+
# Lines ending with one of these tokens are irrelevant for coverage
|
9
|
+
IGNORE_END_WITH = %w|(|
|
10
|
+
|
11
|
+
# Lines containing only one of these keywords are irrelevant for coverage
|
12
|
+
IGNORE_IS = %w|esac if then fi while do done else { } ;;|
|
13
|
+
|
5
14
|
# @param [String] filename File to analyze
|
15
|
+
# @param [Hash] coverage Coverage with executed lines marked
|
6
16
|
# @raise [ArgumentError] if the given +filename+ is invalid.
|
7
|
-
def initialize filename
|
17
|
+
def initialize filename, coverage
|
8
18
|
@filename = File.expand_path(filename)
|
19
|
+
@coverage = coverage
|
9
20
|
|
10
21
|
unless File.file?(@filename)
|
11
22
|
raise ArgumentError, "#{@filename} is not a file"
|
12
23
|
end
|
13
24
|
end
|
14
25
|
|
15
|
-
#
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
26
|
+
# Yields uncovered relevant lines.
|
27
|
+
# @note Uses +@coverage+ to avoid wasting time parsing executed lines.
|
28
|
+
# @return [void]
|
29
|
+
def uncovered_relevant_lines
|
30
|
+
lineno = 0
|
31
|
+
File.open(@filename, 'rb').each_line do |line|
|
32
|
+
if @coverage[lineno] == Bashcov::Line::IGNORED and is_revelant? line
|
33
|
+
yield lineno
|
34
|
+
end
|
35
|
+
lineno +=1
|
20
36
|
end
|
21
|
-
lines
|
22
37
|
end
|
23
38
|
|
24
39
|
private
|
25
40
|
|
26
|
-
def
|
41
|
+
def is_revelant? line
|
27
42
|
line.strip!
|
28
43
|
|
29
|
-
line.empty?
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
line
|
34
|
-
end
|
35
|
-
|
36
|
-
# Lines containing only one of these keywords are irrelevant for coverage
|
37
|
-
def is
|
38
|
-
%w(esac fi then do done else { } ;;)
|
39
|
-
end
|
40
|
-
|
41
|
-
# Lines starting with one of these tokens are irrelevant for coverage
|
42
|
-
def start_with
|
43
|
-
%w(# function)
|
44
|
-
end
|
45
|
-
|
46
|
-
# Lines ending with one of these tokens are irrelevant for coverage
|
47
|
-
def end_with
|
48
|
-
%w(\()
|
44
|
+
!line.empty? and
|
45
|
+
!IGNORE_IS.include? line and
|
46
|
+
!line.start_with?(*IGNORE_START_WITH) and
|
47
|
+
!line.end_with?(*IGNORE_END_WITH) and
|
48
|
+
line !~ /\A\w+\(\)/ # function declared without the 'function' keyword
|
49
49
|
end
|
50
50
|
end
|
51
51
|
end
|
data/lib/bashcov/line.rb
CHANGED
data/lib/bashcov/runner.rb
CHANGED
@@ -7,80 +7,79 @@ module Bashcov
|
|
7
7
|
end
|
8
8
|
|
9
9
|
# Runs the command with appropriate xtrace settings.
|
10
|
-
# @note Binds Bashcov +stdin+ to the program executed.
|
10
|
+
# @note Binds Bashcov +stdin+ to the program being executed.
|
11
11
|
# @return [Process::Status] Status of the executed command
|
12
12
|
def run
|
13
13
|
inject_xtrace_flag!
|
14
14
|
|
15
15
|
@xtrace = Xtrace.new
|
16
16
|
fd = @xtrace.file_descriptor
|
17
|
-
@command = "BASH_XTRACEFD=#{fd} PS4='#{Xtrace
|
17
|
+
@command = "BASH_XTRACEFD=#{fd} PS4='#{Xtrace::PS4}' #{@command}"
|
18
18
|
options = {:in => :in, fd => fd} # bind fds to the child process
|
19
19
|
options.merge!({out: '/dev/null', err: '/dev/null'}) if Bashcov.options.mute
|
20
20
|
|
21
|
-
|
22
|
-
|
21
|
+
command_pid = Process.spawn @command, options # spawn the command
|
22
|
+
xtrace_thread = Thread.new { @xtrace.read } # start processing the xtrace output
|
23
|
+
|
24
|
+
Process.wait command_pid
|
25
|
+
@xtrace.close
|
26
|
+
|
27
|
+
@coverage = xtrace_thread.value # wait for the thread to return
|
23
28
|
|
24
29
|
$?
|
25
30
|
end
|
26
31
|
|
27
32
|
# @return [Hash] Coverage hash of the last run
|
33
|
+
# @note The result is memoized.
|
28
34
|
def result
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
35
|
+
@result ||= begin
|
36
|
+
find_bash_files!
|
37
|
+
expunge_invalid_files!
|
38
|
+
mark_relevant_lines!
|
39
|
+
|
40
|
+
@coverage
|
33
41
|
end
|
42
|
+
end
|
34
43
|
|
35
|
-
|
36
|
-
|
44
|
+
private
|
45
|
+
|
46
|
+
# @note +SHELLOPTS+ must be exported so we use Ruby's {ENV} variable
|
47
|
+
# @return [void]
|
48
|
+
def inject_xtrace_flag!
|
49
|
+
existing_flags = (ENV['SHELLOPTS'] || '').split(':')
|
50
|
+
ENV['SHELLOPTS'] = (existing_flags | ['xtrace']).join(':')
|
37
51
|
end
|
38
52
|
|
39
|
-
#
|
40
|
-
# @return [
|
41
|
-
|
42
|
-
|
43
|
-
Dir[directory].inject({}) do |files, file|
|
44
|
-
absolute_path = File.expand_path(file)
|
45
|
-
next unless File.file?(absolute_path)
|
53
|
+
# Add files which have not been executed at all (i.e. with no coverage)
|
54
|
+
# @return [void]
|
55
|
+
def find_bash_files!
|
56
|
+
return if Bashcov.options.skip_uncovered
|
46
57
|
|
47
|
-
|
58
|
+
Dir["#{Bashcov.root_directory}/**/*.sh"].each do |file|
|
59
|
+
@coverage[file] ||= [] # empty coverage array
|
48
60
|
end
|
49
61
|
end
|
50
62
|
|
51
|
-
# @
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
files[file] ||= Bashcov.coverage_array(file)
|
58
|
-
files[file][lineno] = line if line
|
63
|
+
# @return [void]
|
64
|
+
def expunge_invalid_files!
|
65
|
+
@coverage.each_key do |file|
|
66
|
+
unless File.file? file
|
67
|
+
@coverage.delete file
|
68
|
+
warn "Warning: #{file} was executed but has been deleted since then - it won't be reported in coverage."
|
59
69
|
end
|
60
70
|
end
|
61
|
-
|
62
|
-
files
|
63
71
|
end
|
64
72
|
|
65
|
-
# @param [Hash] files Initial coverage hash
|
66
|
-
# @return [Hash] Given hash ignoring irrelevant lines
|
67
73
|
# @see Lexer
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
lexer.
|
72
|
-
|
74
|
+
# @return [void]
|
75
|
+
def mark_relevant_lines!
|
76
|
+
@coverage.each do |filename, coverage|
|
77
|
+
lexer = Lexer.new(filename, coverage)
|
78
|
+
lexer.uncovered_relevant_lines do |lineno|
|
79
|
+
@coverage[filename][lineno] = Bashcov::Line::UNCOVERED
|
73
80
|
end
|
74
81
|
end
|
75
82
|
end
|
76
|
-
|
77
|
-
private
|
78
|
-
|
79
|
-
def inject_xtrace_flag!
|
80
|
-
# SHELLOPTS must be exported so we use Ruby's ENV variable
|
81
|
-
existing_flags = (ENV['SHELLOPTS'] || '').split(':')
|
82
|
-
ENV['SHELLOPTS'] = (existing_flags | ['xtrace']).join(':')
|
83
|
-
end
|
84
83
|
end
|
85
84
|
end
|
86
85
|
|
data/lib/bashcov/version.rb
CHANGED
data/lib/bashcov/xtrace.rb
CHANGED
@@ -1,70 +1,61 @@
|
|
1
|
-
require 'tempfile'
|
2
|
-
|
3
1
|
module Bashcov
|
4
2
|
# This class manages +xtrace+ output.
|
5
3
|
#
|
6
4
|
# @see Runner
|
7
5
|
class Xtrace
|
8
|
-
#
|
6
|
+
# Prefix used for PS4.
|
7
|
+
# @note The first caracter ('+') will be repeated to indicate the nesting
|
8
|
+
# level.
|
9
|
+
PREFIX = '+BASHCOV> '
|
10
|
+
|
11
|
+
# [String] +PS4+ variable used for xtrace output
|
12
|
+
# @see http://www.gnu.org/software/bash/manual/bashref.html#index-PS4
|
13
|
+
# @see http://stackoverflow.com/questions/59895/can-a-bash-script-tell-what-directory-its-stored-in
|
14
|
+
# @note We use a forward slash as delimiter since it's the only forbidden
|
15
|
+
# character in filenames on Unix and Windows.
|
16
|
+
PS4 = %Q{#{PREFIX}${BASH_SOURCE[0]}/${LINENO}: }
|
17
|
+
|
18
|
+
# Regexp to match xtrace elements.
|
19
|
+
LINE_REGEXP = /\A#{Regexp.escape(PREFIX[0])}+#{PREFIX[1..-1]}(?<filename>.+)\/(?<lineno>\d+): /
|
20
|
+
|
21
|
+
# @return [Hash] Coverage of executed files
|
22
|
+
attr_reader :coverage
|
23
|
+
|
24
|
+
# Creates a temporary file for xtrace output.
|
25
|
+
# @see http://stackoverflow.com/questions/6977561/pipe-vs-temporary-file
|
9
26
|
def initialize
|
10
|
-
@
|
11
|
-
@xtrace_file.unlink # unlink on create so other programs cannot access it
|
27
|
+
@read, @write = IO.pipe
|
12
28
|
end
|
13
29
|
|
14
|
-
# @return [Fixnum] File descriptor of the
|
30
|
+
# @return [Fixnum] File descriptor of the write end of the pipe
|
15
31
|
def file_descriptor
|
16
|
-
@
|
32
|
+
@write.fileno
|
33
|
+
end
|
34
|
+
|
35
|
+
# Closes the pipe for writing.
|
36
|
+
# @return [void]
|
37
|
+
def close
|
38
|
+
@write.close
|
17
39
|
end
|
18
40
|
|
19
|
-
# Parses xtrace output and computes coverage
|
41
|
+
# Parses xtrace output and computes coverage.
|
20
42
|
# @return [Hash] Hash of executed files with coverage information
|
21
|
-
def
|
22
|
-
files = {}
|
43
|
+
def read
|
44
|
+
@files = {}
|
23
45
|
|
24
|
-
@
|
25
|
-
|
26
|
-
match
|
27
|
-
next if match.nil? # multiline instruction
|
46
|
+
@read.each_line do |line|
|
47
|
+
match = line.match(LINE_REGEXP)
|
48
|
+
next if match.nil? # garbage line from multiline instruction
|
28
49
|
|
29
50
|
filename = File.expand_path(match[:filename], Bashcov.root_directory)
|
30
|
-
next if File.directory? filename
|
31
|
-
unless File.file? filename
|
32
|
-
warn "Warning: #{filename} was executed but has been deleted since then - skipping it."
|
33
|
-
next
|
34
|
-
end
|
35
51
|
|
36
52
|
lineno = match[:lineno].to_i - 1
|
37
|
-
files[filename] ||=
|
38
|
-
files[filename][lineno]
|
53
|
+
@files[filename] ||= []
|
54
|
+
@files[filename][lineno] ||= 0
|
55
|
+
@files[filename][lineno] += 1
|
39
56
|
end
|
40
57
|
|
41
|
-
files
|
42
|
-
end
|
43
|
-
|
44
|
-
# @see http://www.gnu.org/software/bash/manual/bashref.html#index-PS4
|
45
|
-
# @return [String] +PS4+ variable used for xtrace output
|
46
|
-
def self.ps4
|
47
|
-
# We use a forward slash as delimiter since it's the only forbidden
|
48
|
-
# character in filenames on Unix and Windows.
|
49
|
-
|
50
|
-
# http://stackoverflow.com/questions/59895/can-a-bash-script-tell-what-directory-its-stored-in
|
51
|
-
%Q{#{prefix}$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")/${LINENO} BASHCOV: }
|
52
|
-
end
|
53
|
-
|
54
|
-
private
|
55
|
-
|
56
|
-
def self.prefix
|
57
|
-
# Note that the first caracter (+) will be repeated to indicate the
|
58
|
-
# nesting level (see depth_character).
|
59
|
-
'+BASHCOV> '
|
60
|
-
end
|
61
|
-
|
62
|
-
def self.depth_character
|
63
|
-
Regexp.escape(prefix[0])
|
64
|
-
end
|
65
|
-
|
66
|
-
def self.line_regexp
|
67
|
-
/\A#{depth_character}+#{prefix[1..-1]}(?<filename>.+)\/(?<lineno>\d+) BASHCOV: /
|
58
|
+
@files
|
68
59
|
end
|
69
60
|
end
|
70
61
|
end
|
data/spec/bashcov/lexer_spec.rb
CHANGED
@@ -1,34 +1,12 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
shared_examples "a bash file" do
|
4
|
-
describe "#irrelevant_lines" do
|
5
|
-
it "returns irrelevant lines" do
|
6
|
-
coverage = expected_coverage[filename]
|
7
|
-
irrelevant_lines = 0.upto(coverage.size - 1).select do |idx|
|
8
|
-
coverage[idx].nil?
|
9
|
-
end
|
10
|
-
|
11
|
-
lexer = Bashcov::Lexer.new filename
|
12
|
-
lexer.irrelevant_lines.should =~ irrelevant_lines
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
3
|
describe Bashcov::Lexer do
|
18
4
|
describe "#initialize" do
|
19
5
|
it "raises if the file is invalid" do
|
20
6
|
expect {
|
21
|
-
Bashcov::Lexer.new 'inexistent_file.exe'
|
7
|
+
Bashcov::Lexer.new 'inexistent_file.exe', nil
|
22
8
|
}.to raise_error ArgumentError
|
23
9
|
end
|
24
10
|
end
|
25
|
-
|
26
|
-
expected_coverage.keys.each do |filename|
|
27
|
-
context filename do
|
28
|
-
it_behaves_like "a bash file" do
|
29
|
-
let(:filename) { filename }
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
11
|
end
|
34
12
|
|
data/spec/bashcov/runner_spec.rb
CHANGED
@@ -1,14 +1,41 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'benchmark'
|
2
3
|
|
3
4
|
describe Bashcov::Runner do
|
4
5
|
let(:runner) { Bashcov::Runner.new test_suite }
|
5
|
-
|
6
|
+
|
7
|
+
before do
|
8
|
+
# 'Bashcov.options.command' is normally set through 'Bashcov.parse_options!'
|
9
|
+
# so we need to stub it
|
10
|
+
Bashcov.options.stub(:command).and_return(test_suite)
|
11
|
+
end
|
6
12
|
|
7
13
|
describe "#run" do
|
8
14
|
it "finds commands in $PATH" do
|
9
15
|
Bashcov::Runner.new('ls -l').run.should be_success
|
10
16
|
end
|
11
17
|
|
18
|
+
it "is fast", speed: :slow do
|
19
|
+
ratio = 0
|
20
|
+
|
21
|
+
3.times do |iteration|
|
22
|
+
t0 = Benchmark.realtime {
|
23
|
+
pid = Process.spawn test_suite, out: '/dev/null', err: '/dev/null'
|
24
|
+
Process.wait pid
|
25
|
+
}
|
26
|
+
$?.should be_success
|
27
|
+
|
28
|
+
run = nil
|
29
|
+
t1 = Benchmark.realtime { run = Bashcov::Runner.new(test_suite).run }
|
30
|
+
run.should be_success
|
31
|
+
|
32
|
+
ratio = (ratio * iteration + t1 / t0) / (iteration + 1)
|
33
|
+
end
|
34
|
+
|
35
|
+
puts "#{ratio} times longer with Bashcov"
|
36
|
+
# XXX no proper assertion - just outputs the ratio
|
37
|
+
end
|
38
|
+
|
12
39
|
context "without a SHELLOPTS variable" do
|
13
40
|
before do
|
14
41
|
ENV['SHELLOPTS'] = nil
|
@@ -32,54 +59,18 @@ describe Bashcov::Runner do
|
|
32
59
|
end
|
33
60
|
end
|
34
61
|
|
35
|
-
describe "#
|
36
|
-
|
37
|
-
subject { files }
|
38
|
-
|
39
|
-
it { should be_a Hash }
|
40
|
-
|
41
|
-
it "contains bash files" do
|
42
|
-
subject.keys.should =~ bash_files
|
43
|
-
end
|
44
|
-
|
45
|
-
it "marks files as uncovered" do
|
46
|
-
subject.values.each do |lines|
|
47
|
-
lines.each { |line| line.should == Bashcov::Line::UNCOVERED }
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
describe "#add_coverage_result" do
|
53
|
-
let(:files) {
|
62
|
+
describe "#result" do
|
63
|
+
it "returns the expected coverage hash" do
|
54
64
|
runner.run
|
55
|
-
|
56
|
-
runner.add_coverage_result files
|
57
|
-
}
|
58
|
-
subject { files }
|
59
|
-
|
60
|
-
it { should be_a Hash }
|
61
|
-
|
62
|
-
it "contains all files" do
|
63
|
-
subject.keys.should =~ bash_files | executed_files
|
65
|
+
runner.result.should eq expected_coverage
|
64
66
|
end
|
65
67
|
|
66
|
-
it "
|
67
|
-
subject.each do |file, lines|
|
68
|
-
lines.each_with_index do |line, lineno|
|
69
|
-
[
|
70
|
-
Bashcov::Line::UNCOVERED,
|
71
|
-
expected_coverage[file][lineno]
|
72
|
-
].should include line
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
describe "#result" do
|
79
|
-
it "returns a valid coverage hash" do
|
68
|
+
it "returns the correct coverage hash" do
|
80
69
|
runner.run
|
81
70
|
|
82
|
-
|
71
|
+
pending "need a context-aware lexer to parse multiline instructions" do
|
72
|
+
runner.result.should eq correct_coverage
|
73
|
+
end
|
83
74
|
end
|
84
75
|
|
85
76
|
context "with options.skip_uncovered = true" do
|
data/spec/spec_helper.rb
CHANGED
data/spec/support/test_app.rb
CHANGED
@@ -2,10 +2,6 @@ def test_app
|
|
2
2
|
File.expand_path("../../test_app", __FILE__)
|
3
3
|
end
|
4
4
|
|
5
|
-
def scripts
|
6
|
-
"#{test_app}/scripts"
|
7
|
-
end
|
8
|
-
|
9
5
|
def test_suite
|
10
6
|
"#{test_app}/test_suite.sh"
|
11
7
|
end
|
@@ -14,33 +10,28 @@ def uncovered_files
|
|
14
10
|
["#{test_app}/never_called.sh"]
|
15
11
|
end
|
16
12
|
|
17
|
-
def executed_files
|
18
|
-
bash_files - uncovered_files + ["#{scripts}/sourced.txt", "#{scripts}/executable"]
|
19
|
-
end
|
20
|
-
|
21
|
-
def bash_files
|
22
|
-
files_in("#{test_app}/**/*.sh")
|
23
|
-
end
|
24
|
-
|
25
|
-
def files_in directory
|
26
|
-
Dir[directory].select { |file| File.file? file }
|
27
|
-
end
|
28
|
-
|
29
13
|
def expected_coverage
|
30
14
|
{
|
31
|
-
"#{test_app}/never_called.sh" => [nil, nil, 0
|
32
|
-
"#{test_app}/scripts/case.sh" => [nil, nil, nil,
|
15
|
+
"#{test_app}/never_called.sh" => [nil, nil, 0],
|
16
|
+
"#{test_app}/scripts/case.sh" => [nil, nil, nil, 2, 1, nil, 0, 0, 1, nil, nil, nil, 1, 1],
|
33
17
|
"#{test_app}/scripts/delete.sh" => [nil, nil, 1, nil, 0, 0, nil, 1, 1],
|
34
|
-
"#{test_app}/scripts/function.sh" => [nil, nil, nil, 2, nil, nil, nil, 1, 1, nil, nil, 1, 1,
|
35
|
-
"#{test_app}/scripts/long_line.sh" => [nil, nil, 1, 1, 1, 0
|
36
|
-
"#{test_app}/scripts/nested/simple.sh" => [nil, nil, nil, nil, 1, 1, nil, 0, nil, nil, 1
|
37
|
-
"#{test_app}/scripts/one_liner.sh" => [nil, nil, 2, nil, 1, nil, 0
|
38
|
-
"#{test_app}/scripts/simple.sh" => [nil, nil, nil, nil, 1, 1, nil, 0, nil, nil, 1
|
39
|
-
"#{test_app}/scripts/source.sh" => [nil, nil, 1, nil, 2
|
40
|
-
"#{test_app}/scripts/sourced.txt" => [nil, nil, 1
|
18
|
+
"#{test_app}/scripts/function.sh" => [nil, nil, nil, 2, nil, nil, nil, 1, 1, nil, nil, nil, nil, 1, nil, nil, 1, 1, 1],
|
19
|
+
"#{test_app}/scripts/long_line.sh" => [nil, nil, 1, 1, 1, 0],
|
20
|
+
"#{test_app}/scripts/nested/simple.sh" => [nil, nil, nil, nil, 1, 1, nil, 0, nil, nil, 1],
|
21
|
+
"#{test_app}/scripts/one_liner.sh" => [nil, nil, 2, nil, 1, nil, 0],
|
22
|
+
"#{test_app}/scripts/simple.sh" => [nil, nil, nil, nil, 1, 1, nil, 0, nil, nil, 1],
|
23
|
+
"#{test_app}/scripts/source.sh" => [nil, nil, 1, nil, 2],
|
24
|
+
"#{test_app}/scripts/sourced.txt" => [nil, nil, 1],
|
25
|
+
"#{test_app}/scripts/unicode.sh" => [nil, nil, nil, 1],
|
41
26
|
"#{test_app}/scripts/multiline.sh" => [nil, nil, 1, 2, 1, 1, 0, nil, nil, 0, 1, 2], # XXX the 3 last lines should be [1, 1, 1] - Bash bug
|
42
|
-
"#{test_app}/scripts/executable" => [nil, nil, 1
|
43
|
-
"#{test_app}/test_suite.sh" => [nil, nil, 2, nil, 1
|
27
|
+
"#{test_app}/scripts/executable" => [nil, nil, 1],
|
28
|
+
"#{test_app}/test_suite.sh" => [nil, nil, 2, nil, 1]
|
44
29
|
}
|
45
30
|
end
|
46
31
|
|
32
|
+
def correct_coverage
|
33
|
+
expected_coverage.merge!({
|
34
|
+
"#{test_app}/scripts/multiline.sh" => [nil, nil, 1, 2, 1, 1, 0, nil, nil, 1, 1, 1]
|
35
|
+
})
|
36
|
+
end
|
37
|
+
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bashcov
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cédric Félizard
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-03-
|
11
|
+
date: 2013-03-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: simplecov
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - ~>
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 0.7.1
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - ~>
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 0.7.1
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -119,6 +119,8 @@ files:
|
|
119
119
|
- .gitignore
|
120
120
|
- .rspec
|
121
121
|
- .travis.yml
|
122
|
+
- CHANGELOG.md
|
123
|
+
- CONTRIBUTING.md
|
122
124
|
- Gemfile
|
123
125
|
- Guardfile
|
124
126
|
- LICENSE.txt
|
@@ -153,6 +155,7 @@ files:
|
|
153
155
|
- spec/test_app/scripts/simple.sh
|
154
156
|
- spec/test_app/scripts/source.sh
|
155
157
|
- spec/test_app/scripts/sourced.txt
|
158
|
+
- spec/test_app/scripts/unicode.sh
|
156
159
|
- spec/test_app/test_suite.sh
|
157
160
|
homepage: https://github.com/infertux/bashcov
|
158
161
|
licenses:
|
@@ -200,5 +203,6 @@ test_files:
|
|
200
203
|
- spec/test_app/scripts/simple.sh
|
201
204
|
- spec/test_app/scripts/source.sh
|
202
205
|
- spec/test_app/scripts/sourced.txt
|
206
|
+
- spec/test_app/scripts/unicode.sh
|
203
207
|
- spec/test_app/test_suite.sh
|
204
208
|
has_rdoc:
|