bashcov 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/.rspec CHANGED
@@ -1,3 +1,4 @@
1
1
  --color
2
2
  --format d
3
3
  --order random
4
+ --profile
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Bashcov [![Build Status](https://secure.travis-ci.org/infertux/bashcov.png?branch=master)](https://travis-ci.org/infertux/bashcov)
1
+ # Bashcov [![Build Status](https://secure.travis-ci.org/infertux/bashcov.png?branch=master)](https://travis-ci.org/infertux/bashcov) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/infertux/bashcov)
2
2
 
3
3
  [bashcov] is [simplecov] for Bash.
4
4
 
data/Rakefile CHANGED
@@ -1,7 +1,9 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rspec/core/rake_task"
3
3
 
4
- RSpec::Core::RakeTask.new(:spec)
4
+ RSpec::Core::RakeTask.new(:spec) do |t|
5
+ t.ruby_opts = '-w'
6
+ end
5
7
 
6
8
  task :default => :spec
7
9
 
data/bashcov.gemspec CHANGED
@@ -18,6 +18,7 @@ Gem::Specification.new do |gem|
18
18
  gem.require_paths = ["lib"]
19
19
 
20
20
  gem.add_dependency 'simplecov'
21
+ gem.add_dependency 'open4'
21
22
 
22
23
  gem.add_development_dependency 'rake'
23
24
  gem.add_development_dependency 'rspec'
data/bin/bashcov CHANGED
@@ -5,14 +5,9 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
 
6
6
  require 'bashcov'
7
7
 
8
- if ARGV.size != 1
9
- $stderr.puts "#{File.basename(__FILE__)} takes exactly one argument."
10
- exit 1
11
- end
8
+ Bashcov.parse_options! ARGV
12
9
 
13
- filename = ARGV[0]
14
-
15
- runner = Bashcov::Runner.new filename
10
+ runner = Bashcov::Runner.new Bashcov.options.filename
16
11
  runner.run
17
12
  coverage = runner.result
18
13
 
data/lib/bashcov/lexer.rb CHANGED
@@ -26,7 +26,7 @@ module Bashcov
26
26
 
27
27
  # Lines containing only one of these keywords are irrelevant for coverage
28
28
  def is
29
- %w(esac fi then do done else { })
29
+ %w(esac fi then do done else { } ;;)
30
30
  end
31
31
 
32
32
  # Lines starting with one of these tokens are irrelevant for coverage
@@ -1,24 +1,46 @@
1
- require 'open3'
1
+ require 'open4'
2
2
 
3
3
  module Bashcov
4
4
  class Runner
5
- attr_reader :output
5
+ attr_reader :stdout, :stderr
6
6
 
7
7
  def initialize filename
8
8
  @filename = File.expand_path(filename)
9
9
  end
10
10
 
11
11
  def run
12
- inject_shellopts_flags
13
-
14
- env = { 'PS4' => Xtrace.ps4 }
15
- stdin, stdout, stderr, wait_thr = Open3.popen3(env, @filename)
16
- exit_status = wait_thr.value # block until process returns
17
- @output = stderr.dup
12
+ setup
13
+
14
+ Open4::popen4(@command) do |pid, stdin, stdout, stderr|
15
+ stdin = $stdin # bind stdin
16
+
17
+ [ # we need threads here to stream output in realtime
18
+ Thread.new { # stdout
19
+ stdout.each do |line|
20
+ $stdout.puts line unless Bashcov.options.mute
21
+ @stdout << line
22
+ end
23
+ },
24
+ Thread.new { # stderr
25
+ 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
+ @stderr << line
31
+ end
32
+ }
33
+ ].map(&:join)
34
+ end
18
35
  end
19
36
 
20
37
  def result
21
- files = find_bash_files "#{Bashcov.root_directory}/**/*.sh"
38
+ if Bashcov.options.skip_uncovered
39
+ files = {}
40
+ else
41
+ files = find_bash_files "#{Bashcov.root_directory}/**/*.sh"
42
+ end
43
+
22
44
  files = add_coverage_result files
23
45
  files = ignore_irrelevant_lines files
24
46
  end
@@ -27,7 +49,7 @@ module Bashcov
27
49
  files = {}
28
50
 
29
51
  # grab all bash files in project root and mark them uncovered
30
- (Dir[directory] - [@filename]).each do |file|
52
+ Dir[directory].each do |file|
31
53
  absolute_path = File.expand_path(file)
32
54
  next unless File.file?(absolute_path)
33
55
 
@@ -38,12 +60,11 @@ module Bashcov
38
60
  end
39
61
 
40
62
  def add_coverage_result files
41
- xtraced_files = Xtrace.new(@output).files
42
- xtraced_files.delete @filename # drop the test suite file
63
+ xtraced_files = Xtrace.new(@stderr).files
43
64
 
44
65
  xtraced_files.each do |file, lines|
45
66
  lines.each_with_index do |line, lineno|
46
- files[file] ||= Bashcov.coverage_array(file) # non .sh files but executed though
67
+ files[file] ||= Bashcov.coverage_array(file)
47
68
  files[file][lineno] = line if line
48
69
  end
49
70
  end
@@ -62,18 +83,20 @@ module Bashcov
62
83
 
63
84
  private
64
85
 
65
- def inject_shellopts_flags
66
- # SHELLOPTS must be exported so we use Ruby's ENV variable
67
- existing_flags = (ENV['SHELLOPTS'] || '').split(shellopts_separator)
68
- ENV['SHELLOPTS'] = (existing_flags | shellopts_flags).join(shellopts_separator)
69
- end
86
+ def setup
87
+ inject_xtrace_flag
88
+
89
+ @stdout = []
90
+ @stderr = []
70
91
 
71
- def shellopts_flags
72
- %w(verbose xtrace)
92
+ @command = "PS4='#{Xtrace.ps4}' #{@filename}"
73
93
  end
74
94
 
75
- def shellopts_separator
76
- ':'
95
+ def inject_xtrace_flag
96
+ # SHELLOPTS must be exported so we use Ruby's ENV variable
97
+ existing_flags = (ENV['SHELLOPTS'] || '').split(':')
98
+ ENV['SHELLOPTS'] = (existing_flags | ['xtrace']).join(':')
77
99
  end
78
100
  end
79
101
  end
102
+
@@ -1,3 +1,3 @@
1
1
  module Bashcov
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
@@ -1,15 +1,19 @@
1
1
  module Bashcov
2
2
  class Xtrace
3
3
  def initialize output
4
- @output = output
4
+ raise "#{output} must be an array" unless output.is_a? Array
5
+ @lines = output
6
+ end
7
+
8
+ def xtrace_output
9
+ @lines.select { |line| line =~ Xtrace.line_regexp }
5
10
  end
6
11
 
7
12
  def files
8
13
  files = {}
9
14
 
10
- @output.readlines.each do |line|
11
- next unless match = line.match(Xtrace.line_regexp)
12
-
15
+ xtrace_output.each do |line|
16
+ match = line.match(Xtrace.line_regexp)
13
17
  filename = File.expand_path(match[:filename], Bashcov.root_directory)
14
18
  next if File.directory? filename
15
19
  raise "#{filename} is not a file" unless File.file? filename
data/lib/bashcov.rb CHANGED
@@ -1,11 +1,58 @@
1
- require "bashcov/version"
2
- require "bashcov/lexer"
3
- require "bashcov/line"
4
- require "bashcov/runner"
5
- require "bashcov/xtrace"
1
+ require 'optparse'
2
+ require 'ostruct'
3
+ require 'bashcov/version'
4
+ require 'bashcov/lexer'
5
+ require 'bashcov/line'
6
+ require 'bashcov/runner'
7
+ require 'bashcov/xtrace'
6
8
 
7
9
  module Bashcov
8
10
  class << self
11
+ attr_reader :options
12
+
13
+ def set_default_options!
14
+ @options ||= OpenStruct.new
15
+ @options.skip_uncovered = false
16
+ @options.mute = false
17
+ end
18
+
19
+ def parse_options! args
20
+ OptionParser.new do |opts|
21
+ opts.banner = "Usage: #{opts.program_name} [options] <filename>"
22
+ opts.version = Bashcov::VERSION
23
+
24
+ opts.separator ""
25
+ opts.separator "Specific options:"
26
+
27
+ opts.on("-s", "--skip-uncovered", "Do not report uncovered files") do |s|
28
+ @options.skip_uncovered = s
29
+ end
30
+
31
+ opts.on("-m", "--mute", "Do not print script output") do |m|
32
+ @options.mute = m
33
+ end
34
+
35
+ opts.separator ""
36
+ opts.separator "Common options:"
37
+
38
+ opts.on_tail("-h", "--help", "Show this message") do
39
+ abort(opts.help)
40
+ end
41
+
42
+ opts.on_tail("--version", "Show version") do
43
+ puts opts.ver
44
+ exit
45
+ end
46
+
47
+ end.parse!(args)
48
+
49
+ if args.one?
50
+ @options.filename = args.shift
51
+ else
52
+ abort("You must give exactly one file to execute.")
53
+ end
54
+ end
55
+
9
56
  def root_directory
10
57
  Dir.getwd
11
58
  end
@@ -20,3 +67,6 @@ module Bashcov
20
67
  end
21
68
  end
22
69
  end
70
+
71
+ Bashcov.set_default_options!
72
+
@@ -3,26 +3,24 @@ require 'spec_helper'
3
3
  shared_examples "a bash file" do
4
4
  describe "#irrelevant_lines" do
5
5
  it "returns irrelevant lines" do
6
- lexer = Bashcov::Lexer.new File.join(scripts, filename)
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
7
12
  lexer.irrelevant_lines.should =~ irrelevant_lines
8
13
  end
9
14
  end
10
15
  end
11
16
 
12
17
  describe Bashcov::Lexer do
13
- it_behaves_like "a bash file" do
14
- let(:filename) { 'simple.sh' }
15
- let(:irrelevant_lines) { [0, 1, 2, 3, 6, 8, 9, 11, 12] }
16
- end
17
-
18
- it_behaves_like "a bash file" do
19
- let(:filename) { 'function.sh' }
20
- let(:irrelevant_lines) { [0, 1, 2, 4, 5, 6, 9, 10, 13] }
21
- end
22
-
23
- it_behaves_like "a bash file" do
24
- let(:filename) { 'sourced.txt' }
25
- let(:irrelevant_lines) { [0, 1, 3] }
18
+ expected_coverage.keys.each do |filename|
19
+ context filename do
20
+ it_behaves_like "a bash file" do
21
+ let(:filename) { filename }
22
+ end
23
+ end
26
24
  end
27
25
  end
28
26
 
@@ -12,18 +12,18 @@ describe Bashcov::Runner do
12
12
 
13
13
  it "adds the flags" do
14
14
  runner.run
15
- ENV['SHELLOPTS'].should == 'verbose:xtrace'
15
+ ENV['SHELLOPTS'].should == 'xtrace'
16
16
  end
17
17
  end
18
18
 
19
19
  context "with an existing SHELLOPTS variable" do
20
20
  before do
21
- ENV['SHELLOPTS'] = 'posix:verbose'
21
+ ENV['SHELLOPTS'] = 'posix'
22
22
  end
23
23
 
24
24
  it "merges the flags" do
25
25
  runner.run
26
- ENV['SHELLOPTS'].should == 'posix:verbose:xtrace'
26
+ ENV['SHELLOPTS'].should == 'posix:xtrace'
27
27
  end
28
28
  end
29
29
  end
@@ -56,7 +56,7 @@ describe Bashcov::Runner do
56
56
  it { should be_a Hash }
57
57
 
58
58
  it "contains all files" do
59
- subject.keys.should =~ all_files
59
+ subject.keys.should =~ bash_files | executed_files
60
60
  end
61
61
 
62
62
  it "adds correct coverage results" do
@@ -77,5 +77,31 @@ describe Bashcov::Runner do
77
77
 
78
78
  runner.result.should == expected_coverage
79
79
  end
80
+
81
+ context "with options.skip_uncovered = true" do
82
+ before do
83
+ Bashcov.options.skip_uncovered = true
84
+ end
85
+
86
+ it "does not include uncovered files" do
87
+ runner.run
88
+ (runner.result.keys & uncovered_files).should be_empty
89
+ end
90
+ end
91
+
92
+ context "with options.mute = false" do
93
+ before do
94
+ Bashcov.options.mute = false
95
+ end
96
+
97
+ it "does not print xtrace output" do
98
+ $stderr.should_receive(:puts).at_least(:once) do |line|
99
+ line.should_not match Bashcov::Xtrace.line_regexp
100
+ end
101
+
102
+ runner.run
103
+ end
104
+ end
80
105
  end
81
106
  end
107
+
@@ -4,7 +4,7 @@ describe Bashcov::Xtrace do
4
4
  let(:xtrace) {
5
5
  runner = Bashcov::Runner.new test_suite
6
6
  runner.run
7
- Bashcov::Xtrace.new runner.output
7
+ Bashcov::Xtrace.new runner.stderr
8
8
  }
9
9
 
10
10
  describe "#files" do
@@ -14,7 +14,7 @@ describe Bashcov::Xtrace do
14
14
  it { should be_a Hash }
15
15
 
16
16
  it "contains expected files" do
17
- subject.keys.should =~ executed_files | [test_suite]
17
+ subject.keys.should =~ executed_files
18
18
  end
19
19
  end
20
20
  end
data/spec/bashcov_spec.rb CHANGED
@@ -1,6 +1,74 @@
1
1
  require 'spec_helper'
2
2
 
3
+ shared_examples "a fatal error" do
4
+ it "outputs to stderr" do
5
+ $stderr.should_receive(:write).at_least(:once)
6
+ ignore_exception { subject }
7
+ end
8
+
9
+ it "exits with non-zero" do
10
+ expect { subject }.to raise_error { |error|
11
+ error.should be_a SystemExit
12
+ error.status.should_not == 0
13
+ }
14
+ end
15
+ end
16
+
3
17
  describe Bashcov do
18
+ describe ".parse_options!" do
19
+ before { @args = [] }
20
+
21
+ subject { Bashcov.parse_options! @args }
22
+
23
+ context "without any options" do
24
+ it_behaves_like "a fatal error"
25
+ end
26
+
27
+ context "with a filename" do
28
+ before { @args << 'script.sh' }
29
+
30
+ context "with the --skip-uncovered flag" do
31
+ before { @args << '--skip-uncovered' }
32
+
33
+ it "sets it properly" do
34
+ subject
35
+ Bashcov.options.skip_uncovered.should be_true
36
+ end
37
+ end
38
+
39
+ context "with the --mute flag" do
40
+ before { @args << '--mute' }
41
+
42
+ it "sets it properly" do
43
+ subject
44
+ Bashcov.options.mute.should be_true
45
+ end
46
+ end
47
+
48
+ context "with the --help flag" do
49
+ before { @args << '--help' }
50
+
51
+ it_behaves_like "a fatal error"
52
+ end
53
+
54
+ context "with the --version flag" do
55
+ before { @args << '--version' }
56
+
57
+ it "outputs to stdout" do
58
+ $stdout.should_receive(:write).at_least(:once)
59
+ ignore_exception { subject }
60
+ end
61
+
62
+ it "exits with zero" do
63
+ expect { subject }.to raise_error { |error|
64
+ error.should be_a SystemExit
65
+ error.status.should == 0
66
+ }
67
+ end
68
+ end
69
+ end
70
+ end
71
+
4
72
  describe ".link" do
5
73
  it "includes the version" do
6
74
  Bashcov.link.should include Bashcov::VERSION
data/spec/spec_helper.rb CHANGED
@@ -11,3 +11,10 @@ require 'bashcov'
11
11
 
12
12
  Dir["./spec/support/**/*.rb"].each { |file| require file }
13
13
 
14
+ RSpec.configure do |config|
15
+ config.before(:each) do
16
+ Bashcov.set_default_options!
17
+ Bashcov.options.mute = true # don't print testsuite output
18
+ end
19
+ end
20
+
@@ -0,0 +1,10 @@
1
+ # Common helpers
2
+
3
+ def ignore_exception
4
+ begin
5
+ yield
6
+ rescue Exception
7
+ # silently ignore the exception
8
+ end
9
+ end
10
+
@@ -10,16 +10,16 @@ def test_suite
10
10
  "#{test_app}/test_suite.sh"
11
11
  end
12
12
 
13
- def executed_files
14
- files_in("#{scripts}/**/*")
13
+ def uncovered_files
14
+ ["#{test_app}/never_called.sh"]
15
15
  end
16
16
 
17
- def bash_files
18
- files_in("#{test_app}/**/*.sh") - [test_suite]
17
+ def executed_files
18
+ bash_files - uncovered_files + ["#{scripts}/sourced.txt"]
19
19
  end
20
20
 
21
- def all_files
22
- files_in("#{test_app}/**/*") - [test_suite]
21
+ def bash_files
22
+ files_in("#{test_app}/**/*.sh")
23
23
  end
24
24
 
25
25
  def files_in directory
@@ -29,13 +29,16 @@ end
29
29
  def expected_coverage
30
30
  {
31
31
  "#{test_app}/never_called.sh" => [nil, nil, 0, nil],
32
+ "#{test_app}/scripts/case.sh" => [nil, nil, nil, 2, 1, nil, 0, 0, 1, nil, nil, nil, 1, 1, nil],
32
33
  "#{test_app}/scripts/function.sh" => [nil, nil, nil, 2, nil, nil, nil, 1, 1, nil, nil, 1, 1, nil],
33
34
  "#{test_app}/scripts/long_line.sh" => [nil, nil, 1, 1, 1, 0, nil],
34
35
  "#{test_app}/scripts/nested/simple.sh" => [nil, nil, nil, nil, 1, 1, nil, 0, nil, nil, 1, nil, nil],
35
36
  "#{test_app}/scripts/one_liner.sh" => [nil, nil, 2, nil, 1, nil, 0, nil, nil],
36
37
  "#{test_app}/scripts/simple.sh" => [nil, nil, nil, nil, 1, 1, nil, 0, nil, nil, 1, nil, nil],
37
38
  "#{test_app}/scripts/source.sh" => [nil, nil, 1, nil, 2, nil],
38
- "#{test_app}/scripts/sourced.txt" => [nil, nil, 1, nil]
39
+ "#{test_app}/scripts/sourced.txt" => [nil, nil, 1, nil],
40
+ "#{test_app}/scripts/stdin.sh" => [nil, nil, 1, 1, 1, nil],
41
+ "#{test_app}/test_suite.sh" => [nil, nil, 2, nil, nil, 2, nil]
39
42
  }
40
43
  end
41
44
 
@@ -0,0 +1 @@
1
+ I'm just a README, I shouldn't interfere with anything.
@@ -0,0 +1 @@
1
+ I'm just a README, I shouldn't interfere with anything.
@@ -0,0 +1,15 @@
1
+ #!/bin/bash
2
+
3
+ function switch() {
4
+ case $1 in
5
+ -h|--help) echo help
6
+ ;;
7
+ -v|--version)
8
+ echo version;;
9
+ *) echo "what?";;
10
+ esac
11
+ }
12
+
13
+ switch -h
14
+ switch bug
15
+
@@ -0,0 +1,6 @@
1
+ #!/bin/bash
2
+
3
+ echo "What's your input?"
4
+ read
5
+ echo "All right, you entered \`${REPLY}'."
6
+
@@ -1,5 +1,7 @@
1
1
  #!/bin/bash
2
2
 
3
3
  cd $(dirname $0)
4
- find scripts -name "*.sh" -exec "{}" \;
4
+
5
+ # `date` is sent on stdin for each file
6
+ date | find scripts -name "*.sh" -exec '{}' \;
5
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.2
4
+ version: 0.0.3
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-13 00:00:00.000000000 Z
12
+ date: 2012-12-14 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: simplecov
@@ -27,6 +27,22 @@ dependencies:
27
27
  - - ! '>='
28
28
  - !ruby/object:Gem::Version
29
29
  version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: open4
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
30
46
  - !ruby/object:Gem::Dependency
31
47
  name: rake
32
48
  requirement: !ruby/object:Gem::Requirement
@@ -121,9 +137,13 @@ files:
121
137
  - spec/bashcov/xtrace_spec.rb
122
138
  - spec/bashcov_spec.rb
123
139
  - spec/spec_helper.rb
140
+ - spec/support/common.rb
124
141
  - spec/support/test_app.rb
125
142
  - spec/test_app/.simplecov
143
+ - spec/test_app/README.md
126
144
  - spec/test_app/never_called.sh
145
+ - spec/test_app/scripts/README.md
146
+ - spec/test_app/scripts/case.sh
127
147
  - spec/test_app/scripts/function.sh
128
148
  - spec/test_app/scripts/long_line.sh
129
149
  - spec/test_app/scripts/nested/simple.sh
@@ -131,6 +151,7 @@ files:
131
151
  - spec/test_app/scripts/simple.sh
132
152
  - spec/test_app/scripts/source.sh
133
153
  - spec/test_app/scripts/sourced.txt
154
+ - spec/test_app/scripts/stdin.sh
134
155
  - spec/test_app/test_suite.sh
135
156
  homepage: https://github.com/infertux/bashcov
136
157
  licenses: []
@@ -162,9 +183,13 @@ test_files:
162
183
  - spec/bashcov/xtrace_spec.rb
163
184
  - spec/bashcov_spec.rb
164
185
  - spec/spec_helper.rb
186
+ - spec/support/common.rb
165
187
  - spec/support/test_app.rb
166
188
  - spec/test_app/.simplecov
189
+ - spec/test_app/README.md
167
190
  - spec/test_app/never_called.sh
191
+ - spec/test_app/scripts/README.md
192
+ - spec/test_app/scripts/case.sh
168
193
  - spec/test_app/scripts/function.sh
169
194
  - spec/test_app/scripts/long_line.sh
170
195
  - spec/test_app/scripts/nested/simple.sh
@@ -172,4 +197,5 @@ test_files:
172
197
  - spec/test_app/scripts/simple.sh
173
198
  - spec/test_app/scripts/source.sh
174
199
  - spec/test_app/scripts/sourced.txt
200
+ - spec/test_app/scripts/stdin.sh
175
201
  - spec/test_app/test_suite.sh