clintegracon 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
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