busser 0.9.1 → 0.9.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8c0be7ee63d2b1d111438e2039187a5283308daae897a27d6028c08d9cefa13d
4
- data.tar.gz: 49e9d09408a9cc284cfb9b43f8daeac1f55eda961ad66697dbefe8741ce2976a
3
+ metadata.gz: cdaf83b2875620df49aa36203b6d4f7f2f6af8065ecbaa218705c949fa96fe04
4
+ data.tar.gz: 40471ed874096d5b55bb8c613a33a7729e761d691f6727e4a848edf0fe398dbd
5
5
  SHA512:
6
- metadata.gz: 71b525573141abe2bf35be2559189a811328e608eaef9a1ead8390595b7d500e76344ef7d29c624028c42d3e7a288a23c41e2e4a09d288734101ee9de2975d35
7
- data.tar.gz: 4d769038c5032cfefdc0b50217f77e6950d2121162c4a07bdbd7d3a7a4b1b794d35a7d501b94ad5621c7628e942cd8d4f56c90d87fa01d0e7aab722e0961fd54
6
+ metadata.gz: 01b77c64592e8bed4f630149be35b89e5b3ac9935119f0c62216b5c16c0ae32ef3feb75c61d68ddf1127ea7bafff7de31f5d11297f2498501d9641b74eab163d
7
+ data.tar.gz: 8efbe414c2a434a98ff91be2f368bd2811cf8110bfce65c337859fbe3806ddca29751f9969a1fa9718c75b8ced4b92176ee33b137bf93b2046cace29fe77f0f6
@@ -43,3 +43,25 @@ jobs:
43
43
  uses: actionshub/publish-gem-to-rubygems@f9126f7a2d36a4fd31a13e78fde5fbdcc4b7b251 # v2.0.6
44
44
  with:
45
45
  token: ${{ secrets.RUBYGEMS_API_KEY }}
46
+
47
+ # actionshub/publish-gem-to-rubygems logs a rejected push and still exits
48
+ # 0, so a release that never reached RubyGems shows up as a green job.
49
+ # Ask RubyGems directly instead of trusting the step. The v2 endpoint is
50
+ # 404 until the exact version is indexed, so -f turns "not there" into a
51
+ # non-zero exit rather than a substring match on `gem list` output.
52
+ - name: Verify the release reached RubyGems
53
+ if: ${{ steps.release.outputs.release_created }}
54
+ env:
55
+ VERSION: ${{ steps.release.outputs.version }}
56
+ run: |
57
+ set -euo pipefail
58
+ name="$(basename "$(ls ./*.gemspec)" .gemspec)"
59
+ for _ in $(seq 1 12); do
60
+ if curl -fsS -o /dev/null "https://rubygems.org/api/v2/rubygems/${name}/versions/${VERSION}.json"; then
61
+ echo "${name} ${VERSION} is on RubyGems"
62
+ exit 0
63
+ fi
64
+ sleep 15
65
+ done
66
+ echo "::error::${name} ${VERSION} never reached RubyGems. A permissions failure in the push step above does not fail that step -- read its log."
67
+ exit 1
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.9.1"
2
+ ".": "0.9.2"
3
3
  }
data/.rubocop.yml CHANGED
@@ -3,7 +3,4 @@ require:
3
3
  - cookstyle/chefstyle
4
4
 
5
5
  AllCops:
6
- TargetRubyVersion: 3.1
7
- Exclude:
8
- - "vendor/**/*"
9
- - "spec/**/*"
6
+ TargetRubyVersion: 3.2
data/CHANGELOG.md CHANGED
@@ -33,6 +33,16 @@ main reason to take this version.
33
33
  * Rewritten README, and development documentation moved to
34
34
  CONTRIBUTING.md ([#63](https://github.com/test-kitchen/busser/pull/63))
35
35
 
36
+ ## [0.9.2](https://github.com/test-kitchen/busser/compare/v0.9.1...v0.9.2) (2026-08-28)
37
+
38
+
39
+ ### Bug Fixes
40
+
41
+ * quote the prepare.sh path before handing it to a shell ([#73](https://github.com/test-kitchen/busser/issues/73)) ([00a24a4](https://github.com/test-kitchen/busser/commit/00a24a4d4c286f50ac698b2cda9e9600b1c54322))
42
+ * restore SSL verification if a plugin postinstall raises ([#72](https://github.com/test-kitchen/busser/issues/72)) ([7f26adb](https://github.com/test-kitchen/busser/commit/7f26adbca1682bd152afb8fc15d90feb55c15d8e))
43
+ * stop `busser plugin list` printing git errors and a phantom plugin ([#77](https://github.com/test-kitchen/busser/issues/77)) ([7a99ebd](https://github.com/test-kitchen/busser/commit/7a99ebd8453ddc836bc0abf0af0f639f07b8d554))
44
+ * tell Thor to exit non-zero when a command fails ([#76](https://github.com/test-kitchen/busser/issues/76)) ([218ba31](https://github.com/test-kitchen/busser/commit/218ba31b812db55cba2479ba6345d305743cb0c1))
45
+
36
46
  ## [0.9.1](https://github.com/test-kitchen/busser/compare/v0.9.0...v0.9.1) (2026-08-28)
37
47
 
38
48
 
data/Gemfile CHANGED
@@ -1,13 +1,20 @@
1
1
  source "https://rubygems.org"
2
2
 
3
- gemspec development_group: :test
3
+ gemspec
4
+
4
5
  group :cookstyle do
5
- gem "cookstyle"
6
+ gem "cookstyle", ">= 9.0"
6
7
  end
7
8
 
8
9
  group :test do
9
- gem "minitest", ">= 6.0"
10
- gem "base64" # cucumber needs it; not a default gem on Ruby 4.0
10
+ gem "aruba", ">= 2.4"
11
+ gem "base64", ">= 0.3" # cucumber needs it; not a default gem on Ruby 4.0
11
12
  gem "cucumber", ">= 11.1"
12
- gem "rake"
13
+ gem "minitest", ">= 6.0"
14
+ gem "mocha", ">= 2.7"
15
+ gem "rake", ">= 13.4"
16
+ end
17
+
18
+ group :development do
19
+ gem "yard", ">= 0.9.37"
13
20
  end
data/README.md CHANGED
@@ -87,6 +87,88 @@ busser test
87
87
  busser test bash minitest
88
88
  ```
89
89
 
90
+ ## Using Busser with Test Kitchen
91
+
92
+ This is how most people meet Busser, and it needs no `busser` commands of your
93
+ own. Select the verifier in `kitchen.yml`:
94
+
95
+ ```yaml
96
+ verifier:
97
+ name: busser
98
+
99
+ suites:
100
+ - name: default
101
+ ```
102
+
103
+ Then put tests in a directory named after the plugin that should run them,
104
+ inside the suite:
105
+
106
+ ```text
107
+ test/integration/default/bash/smoke_test.sh
108
+ ```
109
+
110
+ `kitchen verify` installs Busser and the matching plugin on the instance, then
111
+ runs the suite. Which plugin runs is decided by that directory name alone --
112
+ `bash/` is picked up by busser-bash, `minitest/` by busser-minitest, and so on.
113
+ There is nothing else to configure.
114
+
115
+ ## A worked example, without Test Kitchen
116
+
117
+ To see Busser on its own, set a `BUSSER_ROOT` you can write to:
118
+
119
+ ```bash
120
+ export BUSSER_ROOT=/tmp/busser
121
+ busser setup
122
+ busser plugin install busser-bash
123
+ mkdir -p "$BUSSER_ROOT/suites/bash"
124
+
125
+ cat > "$BUSSER_ROOT/suites/bash/smoke_test.sh" <<'SH'
126
+ #!/usr/bin/env bash
127
+ set -euo pipefail
128
+ echo hello
129
+ SH
130
+
131
+ busser test bash
132
+ ```
133
+
134
+ A passing run looks like this, and exits `0`:
135
+
136
+ ```text
137
+ -----> Running bash test suite
138
+ -----> [bash] smoke_test.sh
139
+ hello
140
+ ```
141
+
142
+ A failing one names the command and exits non-zero:
143
+
144
+ ```text
145
+ -----> Running bash test suite
146
+ -----> [bash] smoke_test.sh
147
+ !!!!!! Command [bash /tmp/busser/suites/bash/smoke_test.sh] exit code was 1
148
+ ```
149
+
150
+ ## When nothing runs
151
+
152
+ A suite whose files do not match what the plugin looks for produces this, and
153
+ **exits `0`**:
154
+
155
+ ```text
156
+ -----> Running bash test suite
157
+ ```
158
+
159
+ No tests ran, and nothing said so. If a suite looks like it is being skipped,
160
+ work through these in order:
161
+
162
+ 1. **Is the plugin installed?** `busser plugin list` shows what is available.
163
+ Without the plugin, `busser test` has no runner for that directory.
164
+ 2. **Is the directory named after the plugin?** Tests for busser-bash live in
165
+ `bash/`, not `tests/` or `scripts/`.
166
+ 3. **Do the filenames match?** Each plugin globs for its own pattern -- for
167
+ example busser-bash takes `*_test.sh` and `*_spec.bash` but ignores
168
+ `mytest.sh`. Each plugin's README states its pattern.
169
+ 4. **Is `BUSSER_ROOT` what you think?** `busser suite path` prints where suites
170
+ are actually being looked for.
171
+
90
172
  ## Contributing
91
173
 
92
174
  Bug reports and pull requests are welcome. See
data/Rakefile CHANGED
@@ -9,7 +9,25 @@ Rake::TestTask.new(:unit) do |t|
9
9
  end
10
10
 
11
11
  Cucumber::Rake::Task.new(:features) do |t|
12
- t.cucumber_opts = ["features", "-x", "--format progress", "--no-color", "-b"]
12
+ t.cucumber_opts = ["features", "--format progress", "--fail-fast"]
13
+ end
14
+
15
+ # yard lives in the :development group, which CI omits when it runs the tests.
16
+ # Requiring it unconditionally would make `rake test` fail there, so the real
17
+ # task is only defined when yard is installed and a stub explains its absence
18
+ # otherwise. Nothing in CI gates on documentation.
19
+ begin
20
+ require "yard"
21
+
22
+ YARD::Rake::YardocTask.new(:doc) do |t|
23
+ t.files = ["lib/**/*.rb"]
24
+ t.options = ["--output-dir", "doc", "--markup", "markdown"]
25
+ end
26
+ rescue LoadError
27
+ desc "Generate YARD documentation (install the development group first)"
28
+ task :doc do
29
+ abort "yard is not available. Run `bundle install --with development` first."
30
+ end
13
31
  end
14
32
 
15
33
  desc "Run all test suites"
data/busser.gemspec CHANGED
@@ -7,25 +7,28 @@ Gem::Specification.new do |spec|
7
7
  spec.version = Busser::VERSION
8
8
  spec.authors = ["Fletcher Nichol"]
9
9
  spec.email = ["fnichol@nichol.ca"]
10
- spec.description = %q{Busser - Runs tests for projects in Test Kitchen}
10
+ spec.description = "Busser - Runs tests for projects in Test Kitchen"
11
11
  spec.summary = spec.description
12
12
  spec.homepage = "https://github.com/test-kitchen/busser"
13
13
  spec.license = "Apache-2.0"
14
14
 
15
- spec.files = `git ls-files`.split($/)
15
+ spec.required_ruby_version = ">= 3.2"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0")
16
18
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
19
  spec.require_paths = ["lib"]
18
20
 
19
- spec.required_ruby_version = ">= 3.2"
21
+ spec.metadata = {
22
+ "bug_tracker_uri" => "#{spec.homepage}/issues",
23
+ "changelog_uri" => "#{spec.homepage}/blob/main/CHANGELOG.md",
24
+ "documentation_uri" => "#{spec.homepage}/blob/main/README.md",
25
+ "homepage_uri" => spec.homepage,
26
+ "source_code_uri" => spec.homepage,
27
+ }
20
28
 
21
29
  # thor 1.1.0 references DidYouMean::SPELL_CHECKERS, removed in Ruby 3.1,
22
30
  # so the CLI crashed on startup on every supported Ruby
23
31
  spec.add_dependency "thor", ">= 1.1"
24
32
  # base64 is no longer a default gem; deserialize needs it at runtime
25
33
  spec.add_dependency "base64"
26
-
27
- spec.add_development_dependency "aruba", ">= 2.0"
28
- spec.add_development_dependency "minitest"
29
- spec.add_development_dependency "mocha"
30
- spec.add_development_dependency "rake"
31
34
  end
@@ -38,6 +38,10 @@ module Busser
38
38
 
39
39
  class_option :perms, desc: "Unix permissions on destination file"
40
40
 
41
+ # Writes a file that was streamed in over stdin, checking it arrived
42
+ # intact before applying its permissions.
43
+ #
44
+ # @return [void]
41
45
  def perform
42
46
  file = File.expand_path(options[:destination])
43
47
  contents = Base64.decode64(STDIN.read)
@@ -36,6 +36,9 @@ module Busser
36
36
  class_option :license, aliases: "-l", default: "apachev2",
37
37
  desc: "License type for gem (apachev2, mit, lgplv3, reserved)"
38
38
 
39
+ # Generates a new runner plugin project.
40
+ #
41
+ # @return [void]
39
42
  def create
40
43
  self.class.source_root(Busser.source_root.join("templates", "plugin"))
41
44
 
@@ -47,6 +50,9 @@ module Busser
47
50
 
48
51
  private
49
52
 
53
+ # Writes the gemspec, Gemfile, Rakefile, README and licence.
54
+ #
55
+ # @return [void]
50
56
  def create_core_files
51
57
  empty_directory(target_dir)
52
58
 
@@ -60,6 +66,9 @@ module Busser
60
66
  create_template("github_workflow.yml.erb", ".github/workflows/test.yml")
61
67
  end
62
68
 
69
+ # Writes lib/, the version file and the runner plugin class.
70
+ #
71
+ # @return [void]
63
72
  def create_source_files
64
73
  empty_directory(File.join(target_dir, "lib/busser", name))
65
74
  empty_directory(File.join(target_dir, "lib/busser/runner_plugin"))
@@ -74,6 +83,9 @@ module Busser
74
83
  )
75
84
  end
76
85
 
86
+ # Writes a starter cucumber suite for the new plugin.
87
+ #
88
+ # @return [void]
77
89
  def create_features_files
78
90
  empty_directory(File.join(target_dir, "features/support"))
79
91
 
@@ -95,6 +107,10 @@ module Busser
95
107
  )
96
108
  end
97
109
 
110
+ # Runs git init in the generated project, so the gemspec's
111
+ # `git ls-files` has something to read.
112
+ #
113
+ # @return [void]
98
114
  def initialize_git
99
115
  inside(target_dir) do
100
116
  run("git init")
@@ -102,14 +118,21 @@ module Busser
102
118
  end
103
119
  end
104
120
 
121
+ # @param erb [String] template path, relative to the templates directory
122
+ # @param dest [String] destination path, relative to the target directory
123
+ # @return [void]
105
124
  def create_template(erb, dest)
106
125
  template(erb, File.join(target_dir, dest), config)
107
126
  end
108
127
 
128
+ # @return [String] directory the new plugin is generated into
109
129
  def target_dir
110
130
  File.join(Dir.pwd, "busser-#{name}")
111
131
  end
112
132
 
133
+ # Values the templates interpolate.
134
+ #
135
+ # @return [Hash] the template binding
113
136
  def config
114
137
  @config ||= begin
115
138
  type_klass_name = "#{::Thor::Util.camel_case(options[:type])}Plugin"
@@ -132,16 +155,19 @@ module Busser
132
155
  end
133
156
  end
134
157
 
158
+ # @return [String] the author name, from git config where available
135
159
  def author
136
160
  git_user_name = `git config user.name`.chomp
137
161
  git_user_name.empty? ? "TODO: Write your name" : git_user_name
138
162
  end
139
163
 
164
+ # @return [String] the author email, from git config where available
140
165
  def email
141
166
  git_user_email = `git config user.email`.chomp
142
167
  git_user_email.empty? ? "TODO: Write your email" : git_user_email
143
168
  end
144
169
 
170
+ # @return [String] full licence text for the chosen licence
145
171
  def license_string
146
172
  case options[:license]
147
173
  when "mit" then "MIT"
@@ -168,6 +194,8 @@ module Busser
168
194
  end
169
195
  end
170
196
 
197
+ # @return [String] the filename the licence is written to, which differs
198
+ # by licence
171
199
  def license_filename
172
200
  case options[:license]
173
201
  when "mit" then "LICENSE.txt"
@@ -178,6 +206,7 @@ module Busser
178
206
  end
179
207
  end
180
208
 
209
+ # @return [String] the licence header comment prepended to source files
181
210
  def license_comment
182
211
  @license_comment ||= IO.read(File.join(target_dir, license_filename))
183
212
  .gsub(/^/, "# ").gsub(/\s+$/, "")
@@ -40,6 +40,9 @@ module Busser
40
40
  class_option :verbose, type: :boolean, default: false,
41
41
  desc: "Set a more verbose output"
42
42
 
43
+ # Installs each requested plugin.
44
+ #
45
+ # @return [void]
43
46
  def install_all
44
47
  if options[:verbose]
45
48
  Gem.configuration.verbose = 2 if options[:verbose]
@@ -53,6 +56,10 @@ module Busser
53
56
 
54
57
  private
55
58
 
59
+ # Installs one plugin and runs its postinstall.
60
+ #
61
+ # @param plugin [String] gem name, optionally suffixed with @version
62
+ # @return [void]
56
63
  def install(plugin)
57
64
  gem_name, version = plugin.split("@")
58
65
  name = gem_name.sub(/^busser-/, "")
@@ -65,6 +72,12 @@ module Busser
65
72
  end
66
73
  end
67
74
 
75
+ # Installs the plugin gem unless it is already available.
76
+ #
77
+ # @param gem [String] the gem name
78
+ # @param version [String, nil] a version requirement, or nil for any
79
+ # @param name [String] short plugin name, for the message
80
+ # @return [Boolean] true if the gem was newly installed
68
81
  def install_plugin_gem(gem, version, name)
69
82
  if internal_plugin?(name) || gem_installed?(gem, version)
70
83
  info "Plugin #{name} already installed"
@@ -76,10 +89,17 @@ module Busser
76
89
  end
77
90
  end
78
91
 
92
+ # @param name [String] short plugin name
93
+ # @return [void]
79
94
  def load_plugin(name)
80
95
  Busser::Plugin.require!(Busser::Plugin.runner_plugin(name))
81
96
  end
82
97
 
98
+ # Runs the plugin's postinstall block, which is where a plugin installs
99
+ # the test framework it drives.
100
+ #
101
+ # @param name [String] short plugin name
102
+ # @return [void]
83
103
  def run_postinstall(name)
84
104
  klass = Busser::Plugin.runner_class(::Thor::Util.camel_case(name))
85
105
  if klass.respond_to?(:run_postinstall)
@@ -100,13 +120,29 @@ module Busser
100
120
  #
101
121
  # Please use with extreme caution.
102
122
  #
123
+ # The restore is in an ensure block. Without one, a postinstall that
124
+ # raised left VERIFY_PEER set to VERIFY_NONE for the rest of the
125
+ # process -- and `busser plugin install` takes a list, so every gem
126
+ # downloaded for every later plugin in that same run would have had its
127
+ # certificate unchecked.
128
+ #
129
+ # @yield the block to run with peer verification dropped
130
+ # @return [void]
103
131
  def drop_ssl_verify_peer
104
132
  before = OpenSSL::SSL::VERIFY_PEER
133
+ set_ssl_verify_peer(OpenSSL::SSL::VERIFY_NONE)
134
+ begin
135
+ yield
136
+ ensure
137
+ set_ssl_verify_peer(before)
138
+ end
139
+ end
140
+
141
+ # @param value [Integer] the OpenSSL verify mode to install
142
+ # @return [void]
143
+ def set_ssl_verify_peer(value)
105
144
  OpenSSL::SSL.send(:remove_const, "VERIFY_PEER")
106
- OpenSSL::SSL.const_set("VERIFY_PEER", OpenSSL::SSL::VERIFY_NONE)
107
- yield
108
- OpenSSL::SSL.send(:remove_const, "VERIFY_PEER")
109
- OpenSSL::SSL.const_set("VERIFY_PEER", before)
145
+ OpenSSL::SSL.const_set("VERIFY_PEER", value)
110
146
  end
111
147
  end
112
148
  end
@@ -28,6 +28,9 @@ module Busser
28
28
  #
29
29
  class PluginList < Busser::Thor::BaseGroup
30
30
 
31
+ # Prints the installed plugins and their versions.
32
+ #
33
+ # @return [void]
31
34
  def list
32
35
  if plugin_data.empty?
33
36
  say "No plugins installed yet"
@@ -38,11 +41,20 @@ module Busser
38
41
 
39
42
  private
40
43
 
44
+ # The dummy runner is a fixture shipped inside busser for its own tests,
45
+ # not something anyone installed. `busser test` already passes over it,
46
+ # so listing it as an installed plugin was inconsistent as well as
47
+ # confusing.
48
+ #
49
+ # @return [Array<Array(String, String)>] each installed plugin's short
50
+ # name and version
41
51
  def plugin_data
42
- @plugin_data ||= Busser::Plugin.runner_plugins.map do |path|
43
- spec = Busser::Plugin.gem_from_path(path)
44
- [File.basename(path), (spec && spec.version)]
45
- end
52
+ @plugin_data ||= Busser::Plugin.runner_plugins
53
+ .reject { |path| File.basename(path) == "dummy" }
54
+ .map do |path|
55
+ spec = Busser::Plugin.gem_from_path(path)
56
+ [File.basename(path), (spec && spec.version)]
57
+ end
46
58
  end
47
59
  end
48
60
  end
@@ -32,6 +32,10 @@ module Busser
32
32
  desc: "Type of binstub file to create (bourne or bat)",
33
33
  default: "bourne"
34
34
 
35
+ # Creates the Busser root and the binstub that runs Busser with an
36
+ # isolated gem environment.
37
+ #
38
+ # @return [void]
35
39
  def perform
36
40
  banner "Setting up Busser"
37
41
  create_busser_root
@@ -40,11 +44,15 @@ module Busser
40
44
 
41
45
  private
42
46
 
47
+ # @return [void]
43
48
  def create_busser_root
44
49
  info "Creating BUSSER_ROOT in #{root_path}"
45
50
  empty_directory(root_path, verbose: false)
46
51
  end
47
52
 
53
+ # Writes the binstub, in whichever flavour --type asked for.
54
+ #
55
+ # @return [void]
48
56
  def generate_busser_binstub
49
57
  info "Creating busser binstub"
50
58
 
@@ -55,6 +63,9 @@ module Busser
55
63
  end
56
64
  end
57
65
 
66
+ # Writes a Windows batch binstub.
67
+ #
68
+ # @return [void]
58
69
  def generate_busser_binstub_for_bat
59
70
  binstub = root_path + "bin/busser.bat"
60
71
  busser_root = root_path.to_s.gsub("/", "\\")
@@ -105,6 +116,9 @@ module Busser
105
116
  end
106
117
  end
107
118
 
119
+ # Writes a Bourne shell binstub, executable.
120
+ #
121
+ # @return [void]
108
122
  def generate_busser_binstub_for_bourne
109
123
  binstub = root_path + "bin/busser"
110
124
 
@@ -144,6 +158,8 @@ module Busser
144
158
  chmod(binstub, 0755, verbose: false)
145
159
  end
146
160
 
161
+ # @return [String] path to the Ruby that is running Busser, in the path
162
+ # style the target binstub needs
147
163
  def ruby_bin
148
164
  result = if (bindir = RbConfig::CONFIG["bindir"])
149
165
  File.join(bindir, "ruby")
@@ -154,18 +170,22 @@ module Busser
154
170
  result
155
171
  end
156
172
 
173
+ # @return [String] the isolated gem home the binstub exports
157
174
  def gem_home
158
175
  Gem.paths.home.dup.tap { |p| p.gsub!("/", "\\") if bat? }
159
176
  end
160
177
 
178
+ # @return [String] the gem path the binstub exports
161
179
  def gem_path
162
180
  Gem.paths.path.join(":").dup.tap { |p| p.gsub!("/", "\\") if bat? }
163
181
  end
164
182
 
183
+ # @return [String] directory holding the real busser executable
165
184
  def gem_bindir
166
185
  Gem.bindir.dup.tap { |p| p.gsub!("/", "\\") if bat? }
167
186
  end
168
187
 
188
+ # @return [Boolean] true when generating a Windows batch binstub
169
189
  def bat?
170
190
  options[:type] == "bat"
171
191
  end
@@ -28,6 +28,9 @@ module Busser
28
28
  #
29
29
  class SuiteCleanup < Busser::Thor::BaseGroup
30
30
 
31
+ # Removes the suites directory and everything in it.
32
+ #
33
+ # @return [void]
31
34
  def cleanup
32
35
  if suite_path.directory?
33
36
  Pathname.glob(suite_path + "*").each do |dir|
@@ -29,6 +29,9 @@ module Busser
29
29
 
30
30
  argument :suite_name, required: false
31
31
 
32
+ # Prints where suites live, or where one named suite lives.
33
+ #
34
+ # @return [void]
32
35
  def path
33
36
  say suite_path(suite_name).to_s
34
37
  end
@@ -15,11 +15,14 @@
15
15
  # See the License for the specific language governing permissions and
16
16
  # limitations under the License.
17
17
 
18
+ require "shellwords" unless defined?(Shellwords)
19
+
18
20
  require "busser/thor"
19
21
  require "busser/plugin"
20
22
 
21
23
  module Busser
22
24
 
25
+ # Namespace for Busser's subcommands.
23
26
  module Command
24
27
 
25
28
  # Test command.
@@ -30,6 +33,10 @@ module Busser
30
33
 
31
34
  argument :plugins, type: :array, required: false
32
35
 
36
+ # Runs each requested plugin's suite, or every installed plugin's suite
37
+ # when none were named.
38
+ #
39
+ # @return [void]
33
40
  def perform
34
41
  Busser::Plugin.runner_plugins(plugins).each do |runner_path|
35
42
  runner = File.basename(runner_path)
@@ -44,26 +51,56 @@ module Busser
44
51
  end
45
52
  end
46
53
 
54
+ # Builds the command that runs a suite's prepare.sh.
55
+ #
56
+ # The path is quoted. It is rooted at BUSSER_ROOT, which the caller
57
+ # chooses, so an unquoted path containing a space would be split by the
58
+ # shell and sh would be handed a fragment instead of the script.
59
+ #
60
+ # @param script [String, Pathname] path to the prepare.sh
61
+ # @return [String] the command to run
62
+ def self.prepare_sh_command(script)
63
+ "/bin/sh #{Shellwords.escape(script.to_s)}"
64
+ end
65
+
47
66
  private
48
67
 
68
+ # The dummy runner exists for Busser's own tests, so it only runs when
69
+ # asked for by name.
70
+ #
71
+ # @param runner [String] short plugin name
72
+ # @return [Boolean] true if this runner should be passed over
49
73
  def skip_runner?(runner)
50
74
  runner == "dummy" && ! Array(plugins).include?("dummy")
51
75
  end
52
76
 
77
+ # Runs whatever preparation the suite ships before its tests.
78
+ #
79
+ # @param runner [String] short plugin name
80
+ # @return [void]
53
81
  def prepare_suite(runner)
54
82
  run_prepare_sh(runner)
55
83
  run_prepare_recipe(runner)
56
84
  end
57
85
 
86
+ # Runs the suite's prepare.sh, if it has one.
87
+ #
88
+ # @param runner [String] short plugin name
89
+ # @return [void]
58
90
  def run_prepare_sh(runner)
59
91
  prepare_sh_script = suite_path(runner).join("prepare.sh")
60
92
 
61
93
  if prepare_sh_script.exist?
62
94
  banner "Preparing #{runner} suite with #{prepare_sh_script}"
63
- run!("/bin/sh #{prepare_sh_script}")
95
+ run!(self.class.prepare_sh_command(prepare_sh_script))
64
96
  end
65
97
  end
66
98
 
99
+ # Warns if the suite still ships the prepare_recipe.rb that Busser
100
+ # dropped support for, rather than ignoring it silently.
101
+ #
102
+ # @param runner [String] short plugin name
103
+ # @return [void]
67
104
  def run_prepare_recipe(runner)
68
105
  prepare_recipe = suite_path(runner).join("prepare_recipe.rb")
69
106