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 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: