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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 88c6a87e619ad3f8d9264f845beac5000ebcd7d3
4
- data.tar.gz: 205f57915123a4b81ec07b73b36362e1b17a3a4a
3
+ metadata.gz: d8360ca3ab8a2d06296fbd911a15045bf10257d1
4
+ data.tar.gz: 9de0f7c886d9ab871d6b17870788bce1925e2b30
5
5
  SHA512:
6
- metadata.gz: c546c3e593a2fe3679ce71e9a72e17a1296438b5e95de7066f44e843c1a906db861fc7028e82d81ce068eddd25699dcd4c069e76b11f8349ee71ae8660bb13db
7
- data.tar.gz: 465f6ff0a51642e534001f7cb81c068a196042f0f4b50d6506f44dbff60e16d7d74d3a5143eb6b91d7afea83a5d9e96174c21e53d03c98bd5b5aaee005a1e04d
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', :version => 2 do
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
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012 Cédric Félizard
1
+ Copyright (c) 2012-2013 Cédric Félizard
2
2
 
3
3
  MIT License
4
4
 
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
- [bashcov] is [simplecov] for Bash.
4
-
5
- Check out the **[demo](http://infertux.github.com/bashcov/test_app/)** - it's worth a thousand words.
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 ./test_suite.sh`
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
- Oddly enough, I didn't find any coverage tool for Bash except [shcov] but as stated [there](http://stackoverflow.com/questions/7188081/code-coverage-tools-for-validating-the-scripts), _shcov_ is:
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
- > somewhat simplistic and doesn't handle all possible cases very well (especially when we're talking about long and complex lines)
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
- Indeed, it doesn't work very well for me.
27
- I have covered lines marked as uncovered and some files completely missed although executed through another script.
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
- _bashcov_ aims to be a neat and working coverage tool backed by _simplecov_ and [simplecov-html].
46
+ ### SimpleCov integration
30
47
 
31
- ## How does it work?
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
- Ruby has a [coverage module](http://www.ruby-doc.org/stdlib-1.9.3/libdoc/coverage/rdoc/Coverage.html) which computes the coverage on demand.
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
- After a bit of parsing, it sends results through _simplecov_ which generates an awesome HTML report.
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
@@ -14,6 +14,6 @@ coverage = runner.result
14
14
  require 'simplecov'
15
15
 
16
16
  SimpleCov.command_name Bashcov.name
17
- # SimpleCov.filters = [] # TODO make sure default filters are okay
17
+ SimpleCov.filters = [] # remove default filters
18
18
  SimpleCov::Result.new(coverage).format!
19
19
 
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] The project's root directory
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 = <<-HELP.gsub!(/^ +/, '').gsub!("\t", ' ' * 4)
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
- # @return [Array] Irrelevant lines
16
- def irrelevant_lines
17
- lines = []
18
- IO.readlines(@filename).each_with_index do |line, lineno|
19
- lines << lineno if is_irrevelant? line
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 is_irrevelant? line
41
+ def is_revelant? line
27
42
  line.strip!
28
43
 
29
- line.empty? or
30
- start_with.any? { |token| line.start_with? token } or
31
- end_with.any? { |token| line.end_with? token } or
32
- is.any? { |keyword| line == keyword } or
33
- line =~ /\A\w+\(\) {/ # function declared like this: "foo() {"
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
@@ -1,5 +1,5 @@
1
1
  module Bashcov
2
- # {Line} represents a line of code
2
+ # {Line} represents a line of code.
3
3
  module Line
4
4
  # Uncovered line
5
5
  # @see http://www.ruby-doc.org/stdlib-1.9.3/libdoc/coverage/rdoc/Coverage.html
@@ -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.ps4}' #{@command}"
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
- pid = Process.spawn @command, options
22
- Process.wait pid
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
- files = if Bashcov.options.skip_uncovered
30
- {}
31
- else
32
- find_bash_files "#{Bashcov.root_directory}/**/*.sh"
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
- files = add_coverage_result files
36
- files = ignore_irrelevant_lines files
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
- # @param [String] directory Directory to scan
40
- # @return [Hash] Coverage hash of Bash files in the given +directory+. All
41
- # files are marked as uncovered.
42
- def find_bash_files directory
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
- files.merge!(absolute_path => Bashcov.coverage_array(absolute_path))
58
+ Dir["#{Bashcov.root_directory}/**/*.sh"].each do |file|
59
+ @coverage[file] ||= [] # empty coverage array
48
60
  end
49
61
  end
50
62
 
51
- # @param [Hash] files Initial coverage hash
52
- # @return [Hash] Given hash including coverage result from {Xtrace}
53
- # @see Xtrace
54
- def add_coverage_result files
55
- @xtrace.files.each do |file, lines|
56
- lines.each_with_index do |line, lineno|
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
- def ignore_irrelevant_lines files
69
- files.each do |filename, lines|
70
- lexer = Lexer.new(filename)
71
- lexer.irrelevant_lines.each do |lineno|
72
- files[filename][lineno] = Bashcov::Line::IGNORED
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
 
@@ -1,4 +1,4 @@
1
1
  module Bashcov
2
- # Bashcov version
3
- VERSION = "0.0.9"
2
+ # [String] Bashcov version
3
+ VERSION = '1.0.0'
4
4
  end
@@ -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
- # Creates a temporary file for xtrace output
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
- @xtrace_file = Tempfile.new 'xtrace_output'
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 output file
30
+ # @return [Fixnum] File descriptor of the write end of the pipe
15
31
  def file_descriptor
16
- @xtrace_file.fileno
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 files
22
- files = {}
43
+ def read
44
+ @files = {}
23
45
 
24
- @xtrace_file.rewind
25
- @xtrace_file.read.each_line do |line|
26
- match = line.match(self.class.line_regexp)
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] ||= Bashcov.coverage_array(filename)
38
- files[filename][lineno] += 1
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
@@ -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
 
@@ -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
- let(:bash_files_glob) { "#{Bashcov.root_directory}/**/*.sh" }
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 "#find_bash_files" do
36
- let(:files) { runner.find_bash_files bash_files_glob }
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
- files = runner.find_bash_files bash_files_glob
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 "adds correct coverage results" do
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
- runner.result.should == expected_coverage
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
@@ -9,7 +9,7 @@ end
9
9
 
10
10
  require 'bashcov'
11
11
 
12
- Dir["./spec/support/**/*.rb"].each { |file| require file }
12
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |file| require file }
13
13
 
14
14
  RSpec.configure do |config|
15
15
  config.before(:each) do
@@ -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, nil],
32
- "#{test_app}/scripts/case.sh" => [nil, nil, nil, 6, 1, nil, 0, 0, 1, nil, nil, nil, 1, 1, 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, nil],
35
- "#{test_app}/scripts/long_line.sh" => [nil, nil, 1, 1, 1, 0, nil],
36
- "#{test_app}/scripts/nested/simple.sh" => [nil, nil, nil, nil, 1, 1, nil, 0, nil, nil, 1, nil, nil],
37
- "#{test_app}/scripts/one_liner.sh" => [nil, nil, 2, nil, 1, nil, 0, nil, nil],
38
- "#{test_app}/scripts/simple.sh" => [nil, nil, nil, nil, 1, 1, nil, 0, nil, nil, 1, nil, nil],
39
- "#{test_app}/scripts/source.sh" => [nil, nil, 1, nil, 2, nil],
40
- "#{test_app}/scripts/sourced.txt" => [nil, nil, 1, nil],
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, nil],
43
- "#{test_app}/test_suite.sh" => [nil, nil, 2, nil, 1, nil]
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
+
@@ -9,6 +9,12 @@ f2() {
9
9
  echo f2
10
10
  }
11
11
 
12
+ __a_bc()
13
+ {
14
+ echo __a_bc
15
+ }
16
+
12
17
  f1
13
18
  f2
19
+ __a_bc
14
20
 
@@ -0,0 +1,4 @@
1
+ #!/bin/bash
2
+
3
+ # ¹²³
4
+ echo 'I am full of unicode characters! äéíóúü¿æ'
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.9
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-05 00:00:00.000000000 Z
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: '0'
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: '0'
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: