bashcov 0.0.9 → 1.0.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 +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 [![Build Status](https://secure.travis-ci.org/infertux/bashcov.png?branch=master)](https://travis-ci.org/infertux/bashcov) [![Dependency Status](https://gemnasium.com/infertux/bashcov.png)](https://gemnasium.com/infertux/bashcov) [![Code Climate](https://codeclimate.com/github/infertux/bashcov.png)](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:
|