bashcov 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.swp
2
+ *.gem
3
+ *.rbc
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --format d
3
+ --order random
data/.travis.yml ADDED
@@ -0,0 +1,10 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - ruby-head
6
+ - rbx-19mode
7
+ matrix:
8
+ allow_failures:
9
+ - rvm: ruby-head
10
+ - rvm: rbx-19mode
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Cédric Félizard
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # Bashcov [![Build Status](https://secure.travis-ci.org/infertux/bashcov.png?branch=master)](https://travis-ci.org/infertux/bashcov)
2
+
3
+ [bashcov] is [simplecov] for Bash.
4
+
5
+ ## Installation
6
+
7
+ `$ gem install bashcov`
8
+
9
+ ## Usage
10
+
11
+ `bashcov ./test_suite.sh`
12
+
13
+ This will create a directory named `coverage/` containing HTML files.
14
+
15
+ Example output: [demo](http://infertux.github.com/bashcov/test_app/)
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 :).
21
+
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:
23
+
24
+ > somewhat simplistic and doesn't handle all possible cases very well (especially when we're talking about long and complex lines)
25
+
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.
28
+
29
+ _bashcov_ aims to be a neat and working coverage tool backed by _simplecov_ and [simplecov-html].
30
+
31
+ ## How does it work?
32
+
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).
35
+
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)).
39
+
40
+ ## License
41
+
42
+ MIT
43
+
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/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+
data/TODO ADDED
@@ -0,0 +1,3 @@
1
+ see if we could implement some features of https://en.wikipedia.org/wiki/Gcov
2
+ tidy up things
3
+ more comprehensive specs
data/bashcov.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'bashcov/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "bashcov"
8
+ gem.version = Bashcov::VERSION
9
+ gem.authors = ["Cédric Félizard"]
10
+ gem.email = ["cedric@felizard.fr"]
11
+ gem.description = %q{Code coverage tool for Bash}
12
+ gem.summary = gem.description
13
+ gem.homepage = "https://github.com/infertux/bashcov"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_dependency 'simplecov'
21
+
22
+ gem.add_development_dependency 'rake'
23
+ gem.add_development_dependency 'rspec'
24
+ end
data/bin/bashcov ADDED
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path('../../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ require 'bashcov'
7
+
8
+ if ARGV.size != 1
9
+ $stderr.puts "#{File.basename(__FILE__)} takes exactly one argument."
10
+ exit 1
11
+ end
12
+
13
+ filename = ARGV[0]
14
+
15
+ runner = Bashcov::Runner.new filename
16
+ runner.run
17
+ coverage = runner.result
18
+
19
+ require 'simplecov'
20
+
21
+ SimpleCov.command_name Bashcov.link
22
+ # SimpleCov.filters = [] # TODO make sure default filters are okay
23
+ SimpleCov::Result.new(coverage).format!
24
+
data/lib/bashcov.rb ADDED
@@ -0,0 +1,22 @@
1
+ require "bashcov/version"
2
+ require "bashcov/lexer"
3
+ require "bashcov/line"
4
+ require "bashcov/runner"
5
+ require "bashcov/xtrace"
6
+
7
+ module Bashcov
8
+ class << self
9
+ def root_directory
10
+ Dir.getwd
11
+ end
12
+
13
+ def coverage_array(filename, fill = Line::UNCOVERED)
14
+ lines = File.readlines(filename).size
15
+ [fill] * lines
16
+ end
17
+
18
+ def link
19
+ '<a href="https://github.com/infertux/bashcov">bashcov</a>'
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,36 @@
1
+ module Bashcov
2
+ class Lexer
3
+ def initialize filename
4
+ @filename = File.expand_path(filename)
5
+ end
6
+
7
+ def irrelevant_lines
8
+ lines = []
9
+ IO.readlines(@filename).each_with_index do |line, lineno|
10
+ lines << lineno if is_irrevelant? line
11
+ end
12
+ lines
13
+ end
14
+
15
+ private
16
+
17
+ def is_irrevelant? line
18
+ line.strip!
19
+ return true if line.empty?
20
+ return true if start_with.any? { |token| line.start_with? token }
21
+ return true if is.any? { |keyword| line =~ /\A#{keyword}\Z/ }
22
+ return true if line =~ /\A\w+\(\) {/ # function declared like this: "foo() {"
23
+ false
24
+ end
25
+
26
+ # Lines containing only one of these keywords are irrelevant for coverage
27
+ def is
28
+ %w(esac fi then do done else { })
29
+ end
30
+
31
+ # Lines starting with one of these keywords are irrelevant for coverage
32
+ def start_with
33
+ %w(# function)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,6 @@
1
+ module Bashcov
2
+ module Line
3
+ UNCOVERED = 0
4
+ IGNORED = nil
5
+ end
6
+ end
@@ -0,0 +1,58 @@
1
+ require 'open3'
2
+
3
+ module Bashcov
4
+ class Runner
5
+ attr_reader :output
6
+
7
+ def initialize filename
8
+ @filename = File.expand_path(filename)
9
+ end
10
+
11
+ def run
12
+ # SHELLOPTS must be exported so we use Ruby's ENV variable
13
+ ENV['SHELLOPTS'] = 'braceexpand:hashall:interactive-comments:posix:verbose:xtrace'
14
+
15
+ command = "PS4='#{Xtrace.ps4}' #{@filename}"
16
+ _, _, @output = Open3.popen3(command)
17
+ end
18
+
19
+ def result
20
+ # 1. Grab all bash files in project root and mark them uncovered
21
+ @files = find_bash_files
22
+
23
+ # 2. Add coverage information from run
24
+ xtraced_files = Xtrace.new(@output).files
25
+ xtraced_files.each do |file, lines|
26
+ next if file == @filename
27
+ lines.each_with_index do |line, index|
28
+ @files[file] ||= Bashcov.coverage_array(file) # non .sh files but executed though
29
+ @files[file][index] = line if line
30
+ end
31
+ end
32
+
33
+ # 3. Ignore irrelevant lines
34
+ @files.each do |filename, lines|
35
+ warn filename unless File.file?(filename)
36
+ next unless File.file?(filename)
37
+ lexer = Lexer.new(filename)
38
+ lexer.irrelevant_lines.each do |lineno|
39
+ @files[filename][lineno] = Bashcov::Line::IGNORED
40
+ end
41
+ end
42
+ end
43
+
44
+ def find_bash_files
45
+ files = {}
46
+
47
+ (Dir["#{Bashcov.root_directory}/**/*.sh"] - [@filename]).each do |file|
48
+ absolute_path = File.expand_path(file)
49
+ next unless File.file?(absolute_path)
50
+
51
+ files[absolute_path] = Bashcov.coverage_array(absolute_path)
52
+ end
53
+
54
+ files
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,3 @@
1
+ module Bashcov
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,52 @@
1
+ module Bashcov
2
+ class Xtrace
3
+ def initialize output
4
+ @output = output
5
+ end
6
+
7
+ def files
8
+ files = {}
9
+
10
+ @output.readlines.each do |line|
11
+ next unless match = line.match(Xtrace.line_regexp)
12
+
13
+ filename = File.expand_path(match[:filename], Bashcov.root_directory)
14
+ next unless File.file? filename
15
+
16
+ lineno = match[:lineno].to_i
17
+ lineno -= 1 if lineno > 0
18
+
19
+ files[filename] ||= Bashcov.coverage_array(filename)
20
+
21
+ files[filename][lineno] += 1
22
+ end
23
+
24
+ files
25
+ end
26
+
27
+ def self.ps4
28
+ # We use a forward slash as delimiter since it's the only forbidden
29
+ # character in filenames on Unix and Windows.
30
+
31
+ # http://stackoverflow.com/questions/59895/can-a-bash-script-tell-what-directory-its-stored-in
32
+ %Q{#{prefix}$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")/${LINENO} BASHCOV}
33
+ end
34
+
35
+ private
36
+
37
+ def self.prefix
38
+ # Note that the first caracter (+) will be repeated to indicate the
39
+ # nesting level (see depth_character).
40
+ '+BASHCOV> '
41
+ end
42
+
43
+ def self.depth_character
44
+ Regexp.escape(prefix[0])
45
+ end
46
+
47
+ def self.line_regexp
48
+ /\A#{depth_character}+#{prefix[1..-1]}(?<filename>.+)\/(?<lineno>\d+) BASHCOV/
49
+ end
50
+ end
51
+ end
52
+
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe Bashcov::Lexer do
4
+ describe "#irrelevant_lines" do
5
+ [
6
+ ['simple.sh', [0, 1, 2, 3, 6, 8, 9, 11, 12]],
7
+ ['function.sh', [0, 1, 2, 4, 5, 6, 9, 10, 13]],
8
+ ['sourced.txt', [0, 1, 3]]
9
+
10
+ ].each do |filename, lines|
11
+ context "for #{filename}" do
12
+ it "returns irrelevant lines" do
13
+ lexer = Bashcov::Lexer.new File.join(scripts, filename)
14
+ lexer.irrelevant_lines.should =~ lines
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe Bashcov::Runner do
4
+ before do
5
+ @runner ||= Bashcov::Runner.new test_suite
6
+ end
7
+
8
+ describe "#find_bash_files" do
9
+ it "returns the list of .sh files in the root directory" do
10
+ files = @runner.find_bash_files
11
+ files.class.should == Hash
12
+ files.values.each do |lines|
13
+ lines.each { |line| line.should == Bashcov::Line::UNCOVERED }
14
+ end
15
+ end
16
+ end
17
+
18
+ describe "#result" do
19
+ it "returns a valid hash" do
20
+ @runner.run
21
+ result = @runner.result
22
+
23
+ result.class.should == Hash
24
+ result.size.should == 8 # FIXME
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+
3
+ describe Bashcov::Xtrace do
4
+ before do
5
+ @runner ||= Bashcov::Runner.new test_suite
6
+ @runner.run
7
+
8
+ @xtrace ||= Bashcov::Xtrace.new @runner.output
9
+ end
10
+
11
+ describe "#files" do
12
+ it "returns a list of executed files" do
13
+ files = @xtrace.files
14
+ files.class.should == Hash
15
+ files.keys.should =~ executed_files + [test_suite]
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,27 @@
1
+ unless RUBY_ENGINE == 'rbx' # coverage support is broken on rbx
2
+ require 'simplecov'
3
+ SimpleCov.start do
4
+ minimum_coverage 98
5
+ add_group "Sources", "lib"
6
+ add_group "Tests", "spec"
7
+ end
8
+ end
9
+
10
+ require 'bashcov'
11
+
12
+ def test_app
13
+ File.expand_path("../test_app", __FILE__)
14
+ end
15
+
16
+ def scripts
17
+ "#{test_app}/scripts"
18
+ end
19
+
20
+ def test_suite
21
+ "#{test_app}/test_suite.sh"
22
+ end
23
+
24
+ def executed_files
25
+ Dir["#{scripts}/**/*"].select { |file| File.file? file }
26
+ end
27
+
@@ -0,0 +1,2 @@
1
+ SimpleCov.add_group 'Bash Scripts', '\.sh$'
2
+ SimpleCov.add_group 'Nested scripts', '/nested'
@@ -0,0 +1,4 @@
1
+ #!/bin/bash
2
+
3
+ echo "I'm never executed by any script"
4
+
@@ -0,0 +1,14 @@
1
+ #!/bin/bash
2
+
3
+ function f1 {
4
+ echo f1
5
+ }
6
+
7
+ f2() {
8
+ f1
9
+ echo f2
10
+ }
11
+
12
+ f1
13
+ f2
14
+
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+
3
+ echo 1 && \
4
+ echo 2 \
5
+ && echo 3 \
6
+ || echo 0
7
+
@@ -0,0 +1,13 @@
1
+ #!/bin/bash
2
+
3
+ # basic test program
4
+
5
+ if true; then
6
+ echo 42
7
+ else
8
+ echo nope
9
+ fi
10
+
11
+ echo "error on stderr" >&2
12
+
13
+
@@ -0,0 +1,9 @@
1
+ #!/bin/bash
2
+
3
+ [ "one" = "two" ] && echo nah || echo yup
4
+
5
+ if false
6
+ then
7
+ never
8
+ fi
9
+
@@ -0,0 +1,13 @@
1
+ #!/bin/bash
2
+
3
+ # basic test program
4
+
5
+ if true; then
6
+ echo 42
7
+ else
8
+ echo nope
9
+ fi
10
+
11
+ echo "error on stderr" >&2
12
+
13
+
@@ -0,0 +1,6 @@
1
+ #!/bin/bash
2
+
3
+ echo "I'm sourcing sourced.txt"
4
+
5
+ . $(dirname $0)/sourced.txt
6
+
@@ -0,0 +1,4 @@
1
+ #!/bin/bash
2
+
3
+ echo "I'm sourced.txt"
4
+
@@ -0,0 +1,5 @@
1
+ #!/bin/bash
2
+
3
+ cd $(dirname $0)
4
+ find scripts -name "*.sh" -exec "{}" \;
5
+
metadata ADDED
@@ -0,0 +1,138 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bashcov
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Cédric Félizard
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-08 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: simplecov
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: Code coverage tool for Bash
63
+ email:
64
+ - cedric@felizard.fr
65
+ executables:
66
+ - bashcov
67
+ extensions: []
68
+ extra_rdoc_files: []
69
+ files:
70
+ - .gitignore
71
+ - .rspec
72
+ - .travis.yml
73
+ - Gemfile
74
+ - LICENSE.txt
75
+ - README.md
76
+ - Rakefile
77
+ - TODO
78
+ - bashcov.gemspec
79
+ - bin/bashcov
80
+ - lib/bashcov.rb
81
+ - lib/bashcov/lexer.rb
82
+ - lib/bashcov/line.rb
83
+ - lib/bashcov/runner.rb
84
+ - lib/bashcov/version.rb
85
+ - lib/bashcov/xtrace.rb
86
+ - spec/bashcov/lexer_spec.rb
87
+ - spec/bashcov/runner_spec.rb
88
+ - spec/bashcov/xtrace_spec.rb
89
+ - spec/spec_helper.rb
90
+ - spec/test_app/.simplecov
91
+ - spec/test_app/never_called.sh
92
+ - spec/test_app/scripts/function.sh
93
+ - spec/test_app/scripts/long_line.sh
94
+ - spec/test_app/scripts/nested/simple.sh
95
+ - spec/test_app/scripts/one_liner.sh
96
+ - spec/test_app/scripts/simple.sh
97
+ - spec/test_app/scripts/source.sh
98
+ - spec/test_app/scripts/sourced.txt
99
+ - spec/test_app/test_suite.sh
100
+ homepage: https://github.com/infertux/bashcov
101
+ licenses: []
102
+ post_install_message:
103
+ rdoc_options: []
104
+ require_paths:
105
+ - lib
106
+ required_ruby_version: !ruby/object:Gem::Requirement
107
+ none: false
108
+ requirements:
109
+ - - ! '>='
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ requirements: []
119
+ rubyforge_project:
120
+ rubygems_version: 1.8.24
121
+ signing_key:
122
+ specification_version: 3
123
+ summary: Code coverage tool for Bash
124
+ test_files:
125
+ - spec/bashcov/lexer_spec.rb
126
+ - spec/bashcov/runner_spec.rb
127
+ - spec/bashcov/xtrace_spec.rb
128
+ - spec/spec_helper.rb
129
+ - spec/test_app/.simplecov
130
+ - spec/test_app/never_called.sh
131
+ - spec/test_app/scripts/function.sh
132
+ - spec/test_app/scripts/long_line.sh
133
+ - spec/test_app/scripts/nested/simple.sh
134
+ - spec/test_app/scripts/one_liner.sh
135
+ - spec/test_app/scripts/simple.sh
136
+ - spec/test_app/scripts/source.sh
137
+ - spec/test_app/scripts/sourced.txt
138
+ - spec/test_app/test_suite.sh