bashcov 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml CHANGED
@@ -1,4 +1,7 @@
1
1
  language: ruby
2
+ after_script:
3
+ - cane
4
+ - yard stats --list-undoc
2
5
  rvm:
3
6
  - 1.9.3
4
7
  - 2.0.0
@@ -7,4 +10,3 @@ rvm:
7
10
  matrix:
8
11
  allow_failures:
9
12
  - rvm: ruby-head
10
- - rvm: rbx-19mode
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [bashcov] is [simplecov] for Bash.
4
4
 
5
- **[Demo](http://infertux.github.com/bashcov/test_app/)**
5
+ Check out the **[demo](http://infertux.github.com/bashcov/test_app/)** - it's worth a thousand words.
6
6
 
7
7
  ## Installation
8
8
 
@@ -37,6 +37,10 @@ After a bit of parsing, it sends results through _simplecov_ which generates an
37
37
 
38
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)).
39
39
 
40
+ ## Todo
41
+
42
+ - semver
43
+
40
44
  ## License
41
45
 
42
46
  MIT
data/bashcov.gemspec CHANGED
@@ -11,6 +11,7 @@ Gem::Specification.new do |gem|
11
11
  gem.description = %q{Code coverage tool for Bash}
12
12
  gem.summary = gem.description
13
13
  gem.homepage = "https://github.com/infertux/bashcov"
14
+ gem.license = 'MIT'
14
15
 
15
16
  gem.files = `git ls-files`.split($/)
16
17
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
@@ -24,4 +25,6 @@ Gem::Specification.new do |gem|
24
25
  gem.add_development_dependency 'rspec'
25
26
  gem.add_development_dependency 'guard-rspec'
26
27
  gem.add_development_dependency 'rb-inotify'
28
+ gem.add_development_dependency 'cane'
29
+ gem.add_development_dependency 'yard'
27
30
  end
data/bin/bashcov CHANGED
@@ -13,7 +13,7 @@ coverage = runner.result
13
13
 
14
14
  require 'simplecov'
15
15
 
16
- SimpleCov.command_name Bashcov.link
16
+ SimpleCov.command_name Bashcov.name
17
17
  # SimpleCov.filters = [] # TODO make sure default filters are okay
18
18
  SimpleCov::Result.new(coverage).format!
19
19
 
data/lib/bashcov.rb CHANGED
@@ -6,23 +6,31 @@ require 'bashcov/line'
6
6
  require 'bashcov/runner'
7
7
  require 'bashcov/xtrace'
8
8
 
9
+ # Bashcov default module
10
+ # @note Keep it short!
9
11
  module Bashcov
10
12
  class << self
13
+ # @return [OpenStruct] Bashcov settings
11
14
  attr_reader :options
12
15
 
16
+ # Sets default options overriding any existing ones.
17
+ # @return [void]
13
18
  def set_default_options!
14
19
  @options ||= OpenStruct.new
15
20
  @options.skip_uncovered = false
16
21
  @options.mute = false
17
22
  end
18
23
 
24
+ # Parses the given CLI arguments and sets +options+.
25
+ # @param [Array] args list of arguments
26
+ # @raise [SystemExit] if invalid arguments are given
27
+ # @return [void]
19
28
  def parse_options! args
20
29
  OptionParser.new do |opts|
21
30
  opts.banner = "Usage: #{opts.program_name} [options] <filename>"
22
31
  opts.version = Bashcov::VERSION
23
32
 
24
- opts.separator ""
25
- opts.separator "Specific options:"
33
+ opts.separator "\nSpecific options:"
26
34
 
27
35
  opts.on("-s", "--skip-uncovered", "Do not report uncovered files") do |s|
28
36
  @options.skip_uncovered = s
@@ -32,8 +40,7 @@ module Bashcov
32
40
  @options.mute = m
33
41
  end
34
42
 
35
- opts.separator ""
36
- opts.separator "Common options:"
43
+ opts.separator "\nCommon options:"
37
44
 
38
45
  opts.on_tail("-h", "--help", "Show this message") do
39
46
  abort(opts.help)
@@ -53,20 +60,32 @@ module Bashcov
53
60
  end
54
61
  end
55
62
 
63
+ # @return [String] The project's root directory
56
64
  def root_directory
57
65
  Dir.getwd
58
66
  end
59
67
 
68
+ # Program name including version for easy consistent output
69
+ # @return [String]
70
+ def name
71
+ "bashcov v#{VERSION}"
72
+ end
73
+
74
+ # Helper to get a pre-filled coverage array for a given file
75
+ # @todo This is generic and should be moved in some helpers file.
76
+ # @api private
77
+ # @param [String] filename The file to cover.
78
+ # @param [nil, Integer] fill Value to fill the array with.
79
+ # @return [Array] An array of the size of the given file.
80
+ # @example
81
+ # coverage_array('file.rb') #=> [0, 0, 0] # assuming file.rb has 3 lines
60
82
  def coverage_array(filename, fill = Line::UNCOVERED)
61
83
  lines = File.readlines(filename).size
62
84
  [fill] * lines
63
85
  end
64
-
65
- def link
66
- %Q|<a href="https://github.com/infertux/bashcov">bashcov</a> v#{VERSION}|
67
- end
68
86
  end
69
87
  end
70
88
 
89
+ # Make sure default options are set
71
90
  Bashcov.set_default_options!
72
91
 
data/lib/bashcov/lexer.rb CHANGED
@@ -1,10 +1,18 @@
1
1
  module Bashcov
2
+ # Simple lexer which analyzes Bash files in order to get information for
3
+ # coverage
2
4
  class Lexer
5
+ # @param [String] filename File to analyze
6
+ # @raise [ArgumentError] if the given +filename+ is invalid.
3
7
  def initialize filename
4
8
  @filename = File.expand_path(filename)
5
- raise "#{@filename} is not a file" unless File.file?(@filename)
9
+
10
+ unless File.file?(@filename)
11
+ raise ArgumentError, "#{@filename} is not a file"
12
+ end
6
13
  end
7
14
 
15
+ # @return [Array] Irrelevant lines
8
16
  def irrelevant_lines
9
17
  lines = []
10
18
  IO.readlines(@filename).each_with_index do |line, lineno|
@@ -17,11 +25,11 @@ module Bashcov
17
25
 
18
26
  def is_irrevelant? line
19
27
  line.strip!
20
- return true if line.empty?
21
- return true if start_with.any? { |token| line.start_with? token }
22
- return true if is.any? { |keyword| line == keyword }
23
- return true if line =~ /\A\w+\(\) {/ # function declared like this: "foo() {"
24
- false
28
+
29
+ line.empty? or
30
+ start_with.any? { |token| line.start_with? token } or
31
+ is.any? { |keyword| line == keyword } or
32
+ line =~ /\A\w+\(\) {/ # function declared like this: "foo() {"
25
33
  end
26
34
 
27
35
  # Lines containing only one of these keywords are irrelevant for coverage
data/lib/bashcov/line.rb CHANGED
@@ -1,8 +1,12 @@
1
1
  module Bashcov
2
+ # {Line} represents a line of code
2
3
  module Line
3
- # see http://www.ruby-doc.org/stdlib-1.9.3/libdoc/coverage/rdoc/Coverage.html
4
-
4
+ # Uncovered line
5
+ # @see http://www.ruby-doc.org/stdlib-1.9.3/libdoc/coverage/rdoc/Coverage.html
5
6
  UNCOVERED = 0
7
+
8
+ # Ignored line
9
+ # @see http://www.ruby-doc.org/stdlib-1.9.3/libdoc/coverage/rdoc/Coverage.html
6
10
  IGNORED = nil
7
11
  end
8
12
  end
@@ -1,64 +1,76 @@
1
1
  require 'open4'
2
2
 
3
3
  module Bashcov
4
+ # Runs a given command capturing output then computes code coverage.
4
5
  class Runner
5
- attr_reader :stdout, :stderr
6
+ # @return [Array] +stdout+ from the last run
7
+ attr_reader :stdout
6
8
 
9
+ # @return [Array] +stderr+ from the last run
10
+ attr_reader :stderr
11
+
12
+ # @param [String] filename Command to run
7
13
  def initialize filename
8
14
  @filename = File.expand_path(filename)
9
15
  end
10
16
 
17
+ # Runs the command capturing +stdout+ and +stderr+.
18
+ # @note Binds Bashcov +stdin+ to the program executed.
19
+ # @note Uses two threads to stream +stdout+ and +stderr+ output in
20
+ # realtime.
21
+ # @return [void]
11
22
  def run
12
23
  setup
13
24
 
14
25
  Open4::popen4(@command) do |pid, stdin, stdout, stderr|
15
26
  stdin = $stdin # bind stdin
16
27
 
17
- [ # we need threads here to stream output in realtime
28
+ [
18
29
  Thread.new { # stdout
19
30
  stdout.each do |line|
20
- $stdout.puts line unless Bashcov.options.mute
21
31
  @stdout << line
32
+ $stdout.puts line unless Bashcov.options.mute
22
33
  end
23
34
  },
24
35
  Thread.new { # stderr
25
36
  stderr.each do |line|
26
- unless Bashcov.options.mute
27
- xtrace = Xtrace.new [line]
28
- $stderr.puts line if xtrace.xtrace_output.empty?
29
- end
30
37
  @stderr << line
38
+ next if Bashcov.options.mute
39
+ xtrace = Xtrace.new [line]
40
+ $stderr.puts line if xtrace.xtrace_output.empty?
31
41
  end
32
42
  }
33
43
  ].map(&:join)
34
44
  end
35
45
  end
36
46
 
47
+ # @return [Hash] Coverage hash of the last run
37
48
  def result
38
- if Bashcov.options.skip_uncovered
39
- files = {}
49
+ files = if Bashcov.options.skip_uncovered
50
+ {}
40
51
  else
41
- files = find_bash_files "#{Bashcov.root_directory}/**/*.sh"
52
+ find_bash_files "#{Bashcov.root_directory}/**/*.sh"
42
53
  end
43
54
 
44
55
  files = add_coverage_result files
45
56
  files = ignore_irrelevant_lines files
46
57
  end
47
58
 
59
+ # @param [String] directory Directory to scan
60
+ # @return [Hash] Coverage hash of Bash files in the given +directory+. All
61
+ # files are marked as uncovered.
48
62
  def find_bash_files directory
49
- files = {}
50
-
51
- # grab all bash files in project root and mark them uncovered
52
- Dir[directory].each do |file|
63
+ Dir[directory].inject({}) do |files, file|
53
64
  absolute_path = File.expand_path(file)
54
65
  next unless File.file?(absolute_path)
55
66
 
56
- files[absolute_path] = Bashcov.coverage_array(absolute_path)
67
+ files.merge!(absolute_path => Bashcov.coverage_array(absolute_path))
57
68
  end
58
-
59
- files
60
69
  end
61
70
 
71
+ # @param [Hash] files Initial coverage hash
72
+ # @return [Hash] Given hash including coverage result from {Xtrace}
73
+ # @see Xtrace
62
74
  def add_coverage_result files
63
75
  xtraced_files = Xtrace.new(@stderr).files
64
76
 
@@ -72,6 +84,9 @@ module Bashcov
72
84
  files
73
85
  end
74
86
 
87
+ # @param [Hash] files Initial coverage hash
88
+ # @return [Hash] Given hash ignoring irrelevant lines
89
+ # @see Lexer
75
90
  def ignore_irrelevant_lines files
76
91
  files.each do |filename, lines|
77
92
  lexer = Lexer.new(filename)
@@ -1,3 +1,4 @@
1
1
  module Bashcov
2
- VERSION = "0.0.3"
2
+ # Bashcov version
3
+ VERSION = "0.0.4"
3
4
  end
@@ -1,14 +1,24 @@
1
1
  module Bashcov
2
+ # This class manages +xtrace+ output.
3
+ #
4
+ # @see Runner
2
5
  class Xtrace
6
+ # @param [Array] output Array of output lines.
7
+ # @raise [ArgumentError] if the given +output+ is not an array
3
8
  def initialize output
4
- raise "#{output} must be an array" unless output.is_a? Array
9
+ raise ArgumentError, "#{output} must be an array" unless output.is_a? Array
5
10
  @lines = output
6
11
  end
7
12
 
13
+ # Filters out non-xtrace lines.
14
+ # @return [Array] xtrace output
8
15
  def xtrace_output
9
16
  @lines.select { |line| line =~ Xtrace.line_regexp }
10
17
  end
11
18
 
19
+ # Parses xtrace output and computes coverage
20
+ # @raise [RuntimeError] on invalid files
21
+ # @return [Hash] Hash of executed files with coverage information
12
22
  def files
13
23
  files = {}
14
24
 
@@ -29,6 +39,8 @@ module Bashcov
29
39
  files
30
40
  end
31
41
 
42
+ # @see http://www.gnu.org/software/bash/manual/bashref.html#index-PS4
43
+ # @return [String] +PS4+ variable used for xtrace output
32
44
  def self.ps4
33
45
  # We use a forward slash as delimiter since it's the only forbidden
34
46
  # character in filenames on Unix and Windows.
@@ -15,6 +15,14 @@ shared_examples "a bash file" do
15
15
  end
16
16
 
17
17
  describe Bashcov::Lexer do
18
+ describe "#initialize" do
19
+ it "raises if the file is invalid" do
20
+ expect {
21
+ Bashcov::Lexer.new 'inexistent_file.exe'
22
+ }.to raise_error ArgumentError
23
+ end
24
+ end
25
+
18
26
  expected_coverage.keys.each do |filename|
19
27
  context filename do
20
28
  it_behaves_like "a bash file" do
data/spec/bashcov_spec.rb CHANGED
@@ -69,9 +69,9 @@ describe Bashcov do
69
69
  end
70
70
  end
71
71
 
72
- describe ".link" do
72
+ describe ".name" do
73
73
  it "includes the version" do
74
- Bashcov.link.should include Bashcov::VERSION
74
+ Bashcov.name.should include Bashcov::VERSION
75
75
  end
76
76
  end
77
77
  end
@@ -15,7 +15,7 @@ def uncovered_files
15
15
  end
16
16
 
17
17
  def executed_files
18
- bash_files - uncovered_files + ["#{scripts}/sourced.txt"]
18
+ bash_files - uncovered_files + ["#{scripts}/sourced.txt", "#{scripts}/executable"]
19
19
  end
20
20
 
21
21
  def bash_files
@@ -38,6 +38,8 @@ def expected_coverage
38
38
  "#{test_app}/scripts/source.sh" => [nil, nil, 1, nil, 2, nil],
39
39
  "#{test_app}/scripts/sourced.txt" => [nil, nil, 1, nil],
40
40
  "#{test_app}/scripts/stdin.sh" => [nil, nil, 1, 1, 1, nil],
41
+ "#{test_app}/scripts/multiline.sh" => [nil, nil, 1, 2, 1, 1, 0, nil],
42
+ "#{test_app}/scripts/executable" => [nil, nil, 1, nil],
41
43
  "#{test_app}/test_suite.sh" => [nil, nil, 2, nil, nil, 2, nil]
42
44
  }
43
45
  end
@@ -0,0 +1,4 @@
1
+ #!/bin/bash
2
+
3
+ echo "Although I'm extension-less, I am an executable Bash script"
4
+
@@ -0,0 +1,8 @@
1
+ #!/bin/bash
2
+
3
+ [ false = true ] || \
4
+ [[ 42 -eq 1337 ]] || [[ 1 -eq 1 ]] \
5
+ && true && \
6
+ echo 'what?' || \
7
+ echo 'no!'
8
+
File without changes
@@ -3,5 +3,5 @@
3
3
  cd $(dirname $0)
4
4
 
5
5
  # `date` is sent on stdin for each file
6
- date | find scripts -name "*.sh" -exec '{}' \;
6
+ date | find scripts -type f -executable -exec '{}' \;
7
7
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bashcov
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-14 00:00:00.000000000 Z
12
+ date: 2013-01-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: simplecov
@@ -107,6 +107,38 @@ dependencies:
107
107
  - - ! '>='
108
108
  - !ruby/object:Gem::Version
109
109
  version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: cane
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: yard
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ type: :development
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
110
142
  description: Code coverage tool for Bash
111
143
  email:
112
144
  - cedric@felizard.fr
@@ -123,7 +155,6 @@ files:
123
155
  - LICENSE.txt
124
156
  - README.md
125
157
  - Rakefile
126
- - TODO
127
158
  - bashcov.gemspec
128
159
  - bin/bashcov
129
160
  - lib/bashcov.rb
@@ -144,8 +175,10 @@ files:
144
175
  - spec/test_app/never_called.sh
145
176
  - spec/test_app/scripts/README.md
146
177
  - spec/test_app/scripts/case.sh
178
+ - spec/test_app/scripts/executable
147
179
  - spec/test_app/scripts/function.sh
148
180
  - spec/test_app/scripts/long_line.sh
181
+ - spec/test_app/scripts/multiline.sh
149
182
  - spec/test_app/scripts/nested/simple.sh
150
183
  - spec/test_app/scripts/one_liner.sh
151
184
  - spec/test_app/scripts/simple.sh
@@ -154,7 +187,8 @@ files:
154
187
  - spec/test_app/scripts/stdin.sh
155
188
  - spec/test_app/test_suite.sh
156
189
  homepage: https://github.com/infertux/bashcov
157
- licenses: []
190
+ licenses:
191
+ - MIT
158
192
  post_install_message:
159
193
  rdoc_options: []
160
194
  require_paths:
@@ -190,8 +224,10 @@ test_files:
190
224
  - spec/test_app/never_called.sh
191
225
  - spec/test_app/scripts/README.md
192
226
  - spec/test_app/scripts/case.sh
227
+ - spec/test_app/scripts/executable
193
228
  - spec/test_app/scripts/function.sh
194
229
  - spec/test_app/scripts/long_line.sh
230
+ - spec/test_app/scripts/multiline.sh
195
231
  - spec/test_app/scripts/nested/simple.sh
196
232
  - spec/test_app/scripts/one_liner.sh
197
233
  - spec/test_app/scripts/simple.sh
@@ -199,3 +235,4 @@ test_files:
199
235
  - spec/test_app/scripts/sourced.txt
200
236
  - spec/test_app/scripts/stdin.sh
201
237
  - spec/test_app/test_suite.sh
238
+ has_rdoc:
data/TODO DELETED
@@ -1,3 +0,0 @@
1
- see if we could implement some features of https://en.wikipedia.org/wiki/Gcov
2
- tidy up things
3
- more comprehensive specs