clintegracon 0.4.1

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.
Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.travis.yml +12 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +158 -0
  7. data/Rakefile +60 -0
  8. data/clintegracon.gemspec +34 -0
  9. data/lib/CLIntegracon.rb +9 -0
  10. data/lib/CLIntegracon/adapter/bacon.rb +208 -0
  11. data/lib/CLIntegracon/configuration.rb +67 -0
  12. data/lib/CLIntegracon/diff.rb +81 -0
  13. data/lib/CLIntegracon/file_tree_spec.rb +213 -0
  14. data/lib/CLIntegracon/file_tree_spec_context.rb +180 -0
  15. data/lib/CLIntegracon/formatter.rb +77 -0
  16. data/lib/CLIntegracon/subject.rb +128 -0
  17. data/lib/CLIntegracon/version.rb +3 -0
  18. data/spec/bacon/execution_output.txt +72 -0
  19. data/spec/bacon/spec_helper.rb +60 -0
  20. data/spec/fixtures/bin/coffeemaker.rb +58 -0
  21. data/spec/integration/coffeemaker_help/after/execution_output.txt +23 -0
  22. data/spec/integration/coffeemaker_help/before/.gitkeep +0 -0
  23. data/spec/integration/coffeemaker_no_milk/after/BlackEye.brewed-coffee +1 -0
  24. data/spec/integration/coffeemaker_no_milk/after/CaPheSuaDa.brewed-coffee +1 -0
  25. data/spec/integration/coffeemaker_no_milk/after/Coffeemakerfile.yml +5 -0
  26. data/spec/integration/coffeemaker_no_milk/after/RedTux.brewed-coffee +1 -0
  27. data/spec/integration/coffeemaker_no_milk/after/execution_output.txt +6 -0
  28. data/spec/integration/coffeemaker_no_milk/before/Coffeemakerfile.yml +5 -0
  29. data/spec/integration/coffeemaker_sweetner_honey/after/Affogato.brewed-coffee +2 -0
  30. data/spec/integration/coffeemaker_sweetner_honey/after/BlackEye.brewed-coffee +2 -0
  31. data/spec/integration/coffeemaker_sweetner_honey/after/Coffeemakerfile.yml +3 -0
  32. data/spec/integration/coffeemaker_sweetner_honey/after/RedTux.brewed-coffee +2 -0
  33. data/spec/integration/coffeemaker_sweetner_honey/after/execution_output.txt +4 -0
  34. data/spec/integration/coffeemaker_sweetner_honey/before/Coffeemakerfile.yml +3 -0
  35. data/spec/unit/adapter/bacon_spec.rb +187 -0
  36. data/spec/unit/configuration_spec.rb +72 -0
  37. metadata +176 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ data.tar.gz: 3bdace78261ca262aa53f9124c582f7e17adbf91
4
+ metadata.gz: ce8b35a77767eefa0b8e5f518edd292225bbf1cb
5
+ SHA512:
6
+ data.tar.gz: 4c055116f2d3f625bd423d2f28c11ebf808eac6983ddd19a3d52f303b011489dafffac365e81afdc35326cdf560ed4c70735b0925a773b8d97e7cd2842a161ea
7
+ metadata.gz: daca9326fc434414d5ed63836b3b743538dd56cb5ec8f49640a4171afc69a272c0b470e3a9f96d49d83587932ad1a6b7f9f3637bf6f98ee7cc3e97473d04ae07
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ /.idea
@@ -0,0 +1,12 @@
1
+ #addons:
2
+ # code_climate:
3
+ # repo_token: #TODO
4
+
5
+ env:
6
+ - RVM_RUBY_VERSION=system
7
+ - RVM_RUBY_VERSION=1.8.7-p358
8
+
9
+ install:
10
+ - bundle install --without=documentation --path ./travis_bundle_dir
11
+
12
+ script: rake spec
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in CLIntegracon.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Marius Rackwitz
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.
@@ -0,0 +1,158 @@
1
+ # CLIntegracon
2
+
3
+ [![Gem](https://img.shields.io/gem/v/clintegracon.svg?style=flat)](http://rubygems.org/gems/clintegracon)
4
+ [![Build Status](https://img.shields.io/travis/mrackwitz/CLIntegracon/master.svg?style=flat)](https://travis-ci.org/mrackwitz/CLIntegracon)
5
+ [![Coverage](https://img.shields.io/codeclimate/coverage/github/mrackwitz/CLIntegracon.svg?style=flat)](https://codeclimate.com/github/mrackwitz/CLIntegracon)
6
+ [![Code Climate](https://img.shields.io/codeclimate/github/mrackwitz/CLIntegracon.svg?style=flat)](https://codeclimate.com/github/mrackwitz/CLIntegracon)
7
+
8
+ CLIntegracon allows you to build *Integration* specs for your *CLI*,
9
+ independent if they are based on Ruby or another technology.
10
+ It is especially useful if your command modifies the file system.
11
+ Furthermore it provides an integration for *Bacon*.
12
+
13
+
14
+ ## Installation
15
+
16
+ Add this line to your application's Gemfile:
17
+
18
+ gem 'clintegracon'
19
+
20
+ And then execute:
21
+
22
+ $ bundle
23
+
24
+ Or install it yourself as:
25
+
26
+ $ gem install clintegracon
27
+
28
+
29
+ ## Usage
30
+
31
+ This description assumes the following file system layout of your CLI project.
32
+ This is not fixed, but if yours differ, you have to change paths accordingly.
33
+
34
+ ```
35
+ ─┬─/ (root)
36
+
37
+ ├─┬─spec
38
+ │ ├───spec_helper.rb
39
+ │ └─┬─integration
40
+ │ ├─┬─arg1
41
+ │ │ ├─┬─before
42
+ │ │ │ ├───source.h
43
+ │ │ │ └───source.c
44
+ │ │ └─┬─after
45
+ │ │ ├───execution_output.txt
46
+ │ │ ├───source.h
47
+ │ │ ├───source.c
48
+ │ │ └───source.o
49
+ │ └─┬─arg2
50
+ │ ├─┬─before
51
+ │ │ …
52
+ │ └─┬─after
53
+ │ …
54
+ └───tmp
55
+ ```
56
+
57
+ ### Bacon
58
+
59
+ 1. Include CLIntegracon in your *spec_helper.rb*
60
+
61
+ ```ruby
62
+ require 'CLIntegracon'
63
+ ```
64
+
65
+ 2. Setup the basic configuration and hook into Bacon as test framework:
66
+
67
+ ```ruby
68
+ CLIntegracon.configure do |c|
69
+ c.context.spec_path = File.expand_path('../integration', __FILE__)
70
+ c.context.temp_path = File.expand_path('../../tmp', __FILE__)
71
+
72
+ c.hook_into :bacon
73
+ end
74
+ ```
75
+
76
+ 3. Describe your specs with the extended DSL:
77
+
78
+ ```ruby
79
+ # Ensure that all the helpers are included in this context
80
+ describe_cli 'coffee-maker' do
81
+
82
+ # Setup our subject
83
+ subject do |s|
84
+ # Provide a display name (optional, default would be 'subject')
85
+ s.name = 'coffee-maker'
86
+
87
+ # Provide the real command line (required)
88
+ s.executable = 'bundle exec ruby spec/fixtures/bin/coffeemaker.rb"'
89
+
90
+ # Set environments variables needed on execution
91
+ s.environment_vars = {
92
+ 'COFFEE_MAKER_FILE' => 'Coffeemakerfile.yml'
93
+ }
94
+
95
+ # Define default arguments
96
+ s.default_args = [
97
+ '--verbose',
98
+ '--no-ansi'
99
+ ]
100
+
101
+ # Replace special paths in execution output by a placeholder, so that the
102
+ # compared outputs doesn't differ dependent on the absolute location where
103
+ # your tested CLI was executed.
104
+ s.has_special_path ROOT.to_s, 'ROOT'
105
+ end
106
+
107
+ context do |c|
108
+ # Ignore certain files ...
109
+ c.ignores '.gitkeep'
110
+
111
+ # ... or explicitly ignore all hidden files. (While the default is that they
112
+ # are included in the file tree diff.)
113
+ c.include_hidden_files = true
114
+ end
115
+
116
+ describe 'Brew recipes' do
117
+
118
+ describe 'without milk' do
119
+ # +behaves_like+ is provided by bacon.
120
+ # +cli_spec+ expects as first argument the directory of the spec, and
121
+ # as second argument the arguments passed to the subject on launch.
122
+ # The defined default arguments will be appended after that.
123
+ # If you need to append arguments after the default arguments, because
124
+ # of the way your command line interface is defined and how its option
125
+ # parser works, you can pass them as third argument to +cli_spec+.
126
+ behaves_like cli_spec('coffeemaker_no_milk', '--no-milk')
127
+
128
+ # Implementation details:
129
+ # +cli_spec+ will define on-the-fly a new shared set of expectations
130
+ # and will return its name and pass it to +behaves_like+, so that it
131
+ # will be immediately executed.
132
+ end
133
+
134
+ describe 'with honey as sweetner' do
135
+ behaves_like cli_spec('coffeemaker_sweetner_honey', '--sweetner=honey')
136
+ end
137
+
138
+ end
139
+
140
+ describe 'Get help' do
141
+ behaves_like cli_spec('coffeemaker_help', '--help')
142
+ end
143
+
144
+ end
145
+ ```
146
+
147
+ 4. Profit
148
+
149
+ ![Bacon Example Terminal Output](/../assets/term-output-bacon.png?raw=true)
150
+
151
+
152
+ ## Contributing
153
+
154
+ 1. Fork it
155
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
156
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
157
+ 4. Push to the branch (`git push origin my-new-feature`)
158
+ 5. Create new Pull Request
@@ -0,0 +1,60 @@
1
+ #encoding: utf-8
2
+
3
+ begin
4
+ require 'bundler/gem_tasks'
5
+ require 'colored'
6
+
7
+ namespace :spec do
8
+
9
+ task :prepare do
10
+ verbose false
11
+ puts 'Prepare …'
12
+ sh 'mkdir -p tmp'
13
+ rm_rf 'tmp/*'
14
+ end
15
+
16
+ desc 'Run the bacon integration spec'
17
+ task :bacon_integration => [:prepare] do
18
+ verbose false
19
+ sh 'rake spec:bacon_integration_runner > tmp/bacon_execution_output.txt' do; end
20
+ puts 'Run bacon spec …'
21
+ sh 'diff spec/bacon/execution_output.txt tmp/bacon_execution_output.txt' do |ok, res|
22
+ if ok
23
+ puts '✓ Spec for bacon passed.'.green
24
+ else
25
+ puts '✗ Spec for bacon failed.'.red
26
+ end
27
+ end
28
+ end
29
+
30
+ desc 'Run the tasks for bacon integration spec verbose and without any outer expectations'
31
+ task :bacon_integration_runner do
32
+ sh [
33
+ 'bundle exec bacon spec/bacon/spec_helper.rb',
34
+ 'sed -e "s|$(echo $GEM_HOME)|\$GEM_HOME|g"',
35
+ 'sed -e "s|$(dirname ~/.)|\$HOME|g"'
36
+ ].join " | "
37
+ end
38
+
39
+ desc 'Run all integration specs'
40
+ task :integration => [
41
+ 'spec:bacon_integration'
42
+ ]
43
+
44
+ desc 'Run all unit specs'
45
+ task :unit do
46
+ sh "bundle exec bacon #{specs('unit/**/*')}"
47
+ end
48
+
49
+ def specs(dir)
50
+ FileList["spec/#{dir}_spec.rb"].shuffle.join(' ')
51
+ end
52
+
53
+ desc 'Run all specs'
54
+ task :all => [:unit, :integration]
55
+
56
+ end
57
+
58
+ desc 'Run all specs'
59
+ task :spec => 'spec:all'
60
+ end
@@ -0,0 +1,34 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'CLIntegracon/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "clintegracon"
8
+ spec.version = CLIntegracon::VERSION
9
+ spec.authors = ["Marius Rackwitz"]
10
+ spec.email = ["git@mariusrackwitz.de"]
11
+ spec.homepage = "https://github.com/mrackwitz/CLIntegracon"
12
+ spec.license = "MIT"
13
+
14
+ spec.summary = "Integration specs for your CLI"
15
+ spec.description = "CLIntegracon allows you to build Integration specs for your CLI," \
16
+ "independent if they are based on Ruby or another technology." \
17
+ "It is especially useful if your command modifies the file system." \
18
+ "It provides an integration for Bacon."
19
+
20
+ spec.files = `git ls-files`.split($/)
21
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
22
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
23
+ spec.require_paths = ["lib"]
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.3"
26
+ spec.add_development_dependency "rake", '~> 10.1.0' # Ruby 1.8.7
27
+ spec.add_development_dependency "bacon"
28
+ spec.add_development_dependency "mocha", "~> 1.0.0" # Ruby 1.8.7
29
+ spec.add_development_dependency "mocha-on-bacon"
30
+ spec.add_development_dependency "claide" # Example CLI
31
+
32
+ spec.add_runtime_dependency 'colored', '~> 1.2'
33
+ spec.add_runtime_dependency 'diffy'
34
+ end
@@ -0,0 +1,9 @@
1
+ require "CLIntegracon/configuration"
2
+ require "CLIntegracon/diff"
3
+ require "CLIntegracon/file_tree_spec"
4
+ require "CLIntegracon/file_tree_spec_context"
5
+ require "CLIntegracon/subject"
6
+ require "CLIntegracon/version"
7
+
8
+ module CLIntegracon
9
+ end
@@ -0,0 +1,208 @@
1
+ require 'colored'
2
+
3
+ # Layout structure
4
+ module CLIntegracon
5
+ module Adapter
6
+ end
7
+ end
8
+
9
+ # Define concrete adapter
10
+ module CLIntegracon::Adapter::Bacon
11
+ module Context
12
+
13
+ # Get or configure the current subject
14
+ #
15
+ # @note On first call this will create a new subject on base of the
16
+ # shared configuration and store it in the ivar `@subject`.
17
+ #
18
+ # @param [Block<(Subject) -> ()>]
19
+ # This block, if given, will be evaluated on the caller.
20
+ # It receives as first argument the subject itself.
21
+ #
22
+ # @return [Subject]
23
+ # the subject
24
+ #
25
+ def subject &block
26
+ @subject ||= CLIntegracon::shared_config.subject.dup
27
+ return @subject if block.nil?
28
+ instance_exec(@subject, &block)
29
+ end
30
+
31
+ # Get or configure the current context
32
+ #
33
+ # @note On first call this will create a new context on base of the
34
+ # shared configuration and store it in the ivar `@context`.
35
+ #
36
+ # @param [Block<(FileTreeSpecContext) -> ()>]
37
+ # This block, if given, will be evaluated on the caller.
38
+ # It receives as first argument the context itself.
39
+ #
40
+ # @return [FileTreeSpecContext]
41
+ # the spec context, will be lazily created if not already present.
42
+ #
43
+ def context &block
44
+ @context ||= CLIntegracon.shared_config.context.dup
45
+ return @context if block.nil?
46
+ instance_exec(@context, &block)
47
+ end
48
+
49
+ # Works like `behaves_like`, but takes arguments for the shared example
50
+ #
51
+ # @param [String] name
52
+ # name of the shared context.
53
+ #
54
+ # @param [...] args
55
+ # params to pass to the shared context
56
+ #
57
+ def behaves_like_a(name, *args)
58
+ instance_exec(*args, &Bacon::Shared[name])
59
+ end
60
+
61
+ # Ad-hoc defines a set of shared expectations to be consumed directly by `behaves_like`.
62
+ # See the following example for usage:
63
+ #
64
+ # behaves_like cli_spec('my_spec_dir', 'install --verbose')
65
+ #
66
+ # @note This expects that a method `context` is defined, which is returning an
67
+ # instance of {FileTreeSpecContext}.
68
+ #
69
+ # @param [String] spec_dir
70
+ # the concrete directory of the spec, see {file_spec}.
71
+ #
72
+ # @param [String] args
73
+ # the additional arguments to pass on launch to {CLIntegracon::Subject}.
74
+ #
75
+ # @return [String]
76
+ # name of the set of shared expectations
77
+ #
78
+ def cli_spec(spec_dir, *args)
79
+ file_spec spec_dir do
80
+ output = subject.launch(*args)
81
+ status = $?
82
+
83
+ it "$ #{subject.name} #{args.join(' ')}" do
84
+ status.should.satisfy("Binary failed\n\n#{output}") do
85
+ status.success?
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ # Ad-hoc defines a set of shared expectations to be consumed directly by `behaves_like`.
92
+ # See the following example for usage:
93
+ #
94
+ # behaves_like file_spec('my_spec_dir') do
95
+ # # do some changes to the current dir
96
+ # end
97
+ #
98
+ # @note This expects that a method `context` is defined, which is returning an
99
+ # instance of {FileTreeSpecContext}.
100
+ #
101
+ # @param [String] spec_dir
102
+ # the concrete directory of the spec to be passed to
103
+ # {FileTreeSpecContext.spec}
104
+ #
105
+ # @param [Block<() -> ()>] block
106
+ # the block which will be executed after the before state is laid out in the
107
+ # temporary directory, which normally will make modifications to file system,
108
+ # which will be compare to the state given in the after directory.
109
+ #
110
+ # @return [String]
111
+ # name of the set of shared expectations
112
+ #
113
+ def file_spec(spec_dir, &block)
114
+ raise ArgumentError.new("Spec directory is missing!") if spec_dir.nil?
115
+
116
+ shared_name = spec_dir
117
+
118
+ shared shared_name do
119
+ context.spec(spec_dir).run do |spec|
120
+ instance_eval &block
121
+
122
+ spec.compare do |diff|
123
+ it diff.relative_path.to_s do
124
+ diff.produced.should.satisfy(spec.formatter.describe_missing_file(diff.relative_path)) do
125
+ diff.produced.exist?
126
+ end
127
+
128
+ diff.produced.should.satisfy(spec.formatter.describe_file_diff(diff)) do
129
+ diff.is_equal?
130
+ end
131
+ end
132
+ end
133
+
134
+ spec.check_unexpected_files do |files|
135
+ it "should not produce unexpected files" do
136
+ files.should.satisfy(spec.formatter.describe_unexpected_files(files)) do
137
+ files.size == 0
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
143
+
144
+ shared_name
145
+ end
146
+
147
+ end
148
+
149
+ # Describe a command line interface
150
+ # This method basically behaves like {Bacon::Context.describe}, but it provides
151
+ # automatically the methods #subject, #context, #cli_spec and #file_spec.
152
+ #
153
+ # @param [String] subject_name
154
+ # the subject name will be used as first argument to initialize
155
+ # a new {CLIntegracon::Subject}, which will be accessible in the
156
+ # spec by #subject.
157
+ #
158
+ # @param [Hash<Symbol,String>] context_options
159
+ # the options to configure this spec context, could be one or more of:
160
+ # * :executable: the executable used to initialize {CLIntegracon::Subject}
161
+ # if not given, will fallback to param {subject_name}.
162
+ #
163
+ # @param [Block<() -> ()>] block
164
+ # the block to provide further sub-specs or requirements, as
165
+ # known from {Bacon::Context.describe}
166
+ #
167
+ def describe_cli(subject_name, context_options = {}, &block)
168
+ context = describe subject_name do
169
+ # Make Context methods available
170
+ # WORKAROUND: Bacon auto-inherits singleton methods to child contexts
171
+ # by using the runtime and won't include methods in modules included
172
+ # by the parent context. We have to ensure that the methods will be
173
+ # accessible by the child contexts by defining them as singleton methods.
174
+ extended = self.extend Context
175
+ Context.instance_methods.each do |method|
176
+ class << self; self end.instance_eval do
177
+ unbound_method = extended.method(method).unbind
178
+
179
+ send :define_method, method do |*args, &b|
180
+ unbound_method.bind(self).call(*args, &b)
181
+ end
182
+ end
183
+ end
184
+
185
+ subject do |s|
186
+ s.name = subject_name
187
+ s.executable = context_options[:executable] || subject_name
188
+ end
189
+
190
+ instance_eval &block
191
+ end
192
+
193
+ Bacon::ErrorLog.gsub! %r{^.*lib/CLIntegracon/.*\n}, ''
194
+
195
+ context
196
+ end
197
+
198
+ end
199
+
200
+ # Make #describe_cli global available
201
+ extend CLIntegracon::Adapter::Bacon
202
+
203
+ # Patch Bacon::Context to support #describe_cli
204
+ module Bacon
205
+ class Context
206
+ include CLIntegracon::Adapter::Bacon
207
+ end
208
+ end