busser 0.9.0 → 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 +4 -4
- data/.github/workflows/publish.yml +22 -0
- data/.release-please-manifest.json +1 -1
- data/.rubocop.yml +1 -4
- data/CHANGELOG.md +18 -0
- data/Gemfile +12 -5
- data/README.md +82 -0
- data/Rakefile +19 -1
- data/busser.gemspec +11 -8
- data/features/plugin_create_command.feature +7 -9
- data/lib/busser/command/deserialize.rb +4 -0
- data/lib/busser/command/plugin_create.rb +46 -3
- data/lib/busser/command/plugin_install.rb +40 -4
- data/lib/busser/command/plugin_list.rb +16 -4
- data/lib/busser/command/setup.rb +37 -3
- data/lib/busser/command/suite_cleanup.rb +3 -0
- data/lib/busser/command/suite_path.rb +3 -0
- data/lib/busser/command/test.rb +38 -1
- data/lib/busser/cucumber/hooks.rb +9 -0
- data/lib/busser/helpers.rb +24 -0
- data/lib/busser/plugin.rb +51 -9
- data/lib/busser/rubygems.rb +20 -0
- data/lib/busser/runner_plugin/dummy.rb +3 -0
- data/lib/busser/runner_plugin.rb +5 -0
- data/lib/busser/thor.rb +23 -0
- data/lib/busser/ui.rb +63 -0
- data/lib/busser/version.rb +2 -1
- data/release-please-config.json +3 -1
- data/spec/busser/command/deserialize_spec.rb +0 -1
- data/spec/busser/command/plugin_create_spec.rb +174 -0
- data/spec/busser/command/plugin_install_spec.rb +44 -0
- data/spec/busser/command/plugin_list_spec.rb +49 -0
- data/spec/busser/command/setup_spec.rb +120 -0
- data/spec/busser/command/test_spec.rb +36 -0
- data/spec/busser/helpers_spec.rb +10 -10
- data/spec/busser/plugin_spec.rb +0 -1
- data/spec/busser/ui_spec.rb +56 -10
- data/spec/spec_helper.rb +2 -3
- data/templates/plugin/Rakefile.erb +4 -24
- data/templates/plugin/features_env.rb.erb +4 -4
- data/templates/plugin/gemspec.erb +7 -8
- data/templates/plugin/github_workflow.yml.erb +26 -0
- metadata +13 -60
- data/templates/plugin/tailor.erb +0 -4
- data/templates/plugin/travis.yml.erb +0 -11
data/lib/busser/command/setup.rb
CHANGED
|
@@ -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("/", "\\")
|
|
@@ -79,9 +90,19 @@ module Busser
|
|
|
79
90
|
SET "GEM_PATH=#{gem_path}"
|
|
80
91
|
SET "GEM_CACHE=#{gem_home}\\cache"
|
|
81
92
|
|
|
82
|
-
REM
|
|
93
|
+
REM Keep the calling Ruby environment out of this one. Clearing
|
|
94
|
+
REM RUBYOPT is no longer enough: RubyGems requires bundler/setup
|
|
95
|
+
REM whenever BUNDLER_SETUP is set, and bundler then points GEM_HOME
|
|
96
|
+
REM at the calling project's bundle, undoing the isolation above.
|
|
83
97
|
SET RUBYOPT=
|
|
84
98
|
SET GEMRC=
|
|
99
|
+
SET RUBYLIB=
|
|
100
|
+
SET BUNDLER_SETUP=
|
|
101
|
+
SET BUNDLER_VERSION=
|
|
102
|
+
SET BUNDLE_BIN_PATH=
|
|
103
|
+
SET BUNDLE_GEMFILE=
|
|
104
|
+
SET BUNDLE_LOCKFILE=
|
|
105
|
+
SET BUNDLE_PATH=
|
|
85
106
|
|
|
86
107
|
REM Call the actual Busser bin with our arguments
|
|
87
108
|
"#{ruby_bin}" "#{gem_bindir}\\busser" %*
|
|
@@ -95,6 +116,9 @@ module Busser
|
|
|
95
116
|
end
|
|
96
117
|
end
|
|
97
118
|
|
|
119
|
+
# Writes a Bourne shell binstub, executable.
|
|
120
|
+
#
|
|
121
|
+
# @return [void]
|
|
98
122
|
def generate_busser_binstub_for_bourne
|
|
99
123
|
binstub = root_path + "bin/busser"
|
|
100
124
|
|
|
@@ -120,8 +144,12 @@ module Busser
|
|
|
120
144
|
GEM_PATH="#{gem_path}"; export GEM_PATH
|
|
121
145
|
GEM_CACHE="#{gem_home}/cache"; export GEM_CACHE
|
|
122
146
|
|
|
123
|
-
#
|
|
124
|
-
|
|
147
|
+
# Keep the calling Ruby environment out of this one. Unsetting
|
|
148
|
+
# RUBYOPT is no longer enough: RubyGems requires bundler/setup
|
|
149
|
+
# whenever BUNDLER_SETUP is set, and bundler then points GEM_HOME
|
|
150
|
+
# at the calling project's bundle, undoing the isolation above.
|
|
151
|
+
unset RUBYOPT GEMRC RUBYLIB BUNDLER_SETUP BUNDLER_VERSION
|
|
152
|
+
unset BUNDLE_BIN_PATH BUNDLE_GEMFILE BUNDLE_LOCKFILE BUNDLE_PATH
|
|
125
153
|
|
|
126
154
|
# Call the actual Busser bin with our arguments
|
|
127
155
|
exec "#{ruby_bin}" "#{gem_bindir}/busser" "$@"
|
|
@@ -130,6 +158,8 @@ module Busser
|
|
|
130
158
|
chmod(binstub, 0755, verbose: false)
|
|
131
159
|
end
|
|
132
160
|
|
|
161
|
+
# @return [String] path to the Ruby that is running Busser, in the path
|
|
162
|
+
# style the target binstub needs
|
|
133
163
|
def ruby_bin
|
|
134
164
|
result = if (bindir = RbConfig::CONFIG["bindir"])
|
|
135
165
|
File.join(bindir, "ruby")
|
|
@@ -140,18 +170,22 @@ module Busser
|
|
|
140
170
|
result
|
|
141
171
|
end
|
|
142
172
|
|
|
173
|
+
# @return [String] the isolated gem home the binstub exports
|
|
143
174
|
def gem_home
|
|
144
175
|
Gem.paths.home.dup.tap { |p| p.gsub!("/", "\\") if bat? }
|
|
145
176
|
end
|
|
146
177
|
|
|
178
|
+
# @return [String] the gem path the binstub exports
|
|
147
179
|
def gem_path
|
|
148
180
|
Gem.paths.path.join(":").dup.tap { |p| p.gsub!("/", "\\") if bat? }
|
|
149
181
|
end
|
|
150
182
|
|
|
183
|
+
# @return [String] directory holding the real busser executable
|
|
151
184
|
def gem_bindir
|
|
152
185
|
Gem.bindir.dup.tap { |p| p.gsub!("/", "\\") if bat? }
|
|
153
186
|
end
|
|
154
187
|
|
|
188
|
+
# @return [Boolean] true when generating a Windows batch binstub
|
|
155
189
|
def bat?
|
|
156
190
|
options[:type] == "bat"
|
|
157
191
|
end
|
data/lib/busser/command/test.rb
CHANGED
|
@@ -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!(
|
|
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
|
|
|
@@ -14,10 +14,19 @@ After do
|
|
|
14
14
|
end
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
+
# Stashes an environment variable so a scenario can change it and have the
|
|
18
|
+
# original put back afterwards.
|
|
19
|
+
#
|
|
20
|
+
# @param key [String] the variable name
|
|
21
|
+
# @return [String, nil] the stashed value
|
|
17
22
|
def backup_envvar(key)
|
|
18
23
|
ENV["_CUKE_#{key}"] = ENV[key]
|
|
19
24
|
end
|
|
20
25
|
|
|
26
|
+
# Puts back a variable stashed by {backup_envvar}.
|
|
27
|
+
#
|
|
28
|
+
# @param key [String] the variable name
|
|
29
|
+
# @return [String, nil] the restored value
|
|
21
30
|
def restore_envvar(key)
|
|
22
31
|
ENV[key] = ENV.delete("_CUKE_#{key}")
|
|
23
32
|
end
|
data/lib/busser/helpers.rb
CHANGED
|
@@ -29,22 +29,41 @@ module Busser
|
|
|
29
29
|
|
|
30
30
|
module_function
|
|
31
31
|
|
|
32
|
+
# Path to the suites directory, or to one suite inside it.
|
|
33
|
+
#
|
|
34
|
+
# @param name [String, nil] a suite name, or nil for the containing
|
|
35
|
+
# directory
|
|
36
|
+
# @return [Pathname] absolute path beneath the Busser root
|
|
32
37
|
def suite_path(name = nil)
|
|
33
38
|
path = root_path + "suites"
|
|
34
39
|
path += name if name
|
|
35
40
|
path.expand_path
|
|
36
41
|
end
|
|
37
42
|
|
|
43
|
+
# Path to the vendor directory, or to one vendored product inside it.
|
|
44
|
+
#
|
|
45
|
+
# @param product [String, nil] a product name, or nil for the containing
|
|
46
|
+
# directory
|
|
47
|
+
# @return [Pathname] absolute path beneath the Busser root
|
|
38
48
|
def vendor_path(product = nil)
|
|
39
49
|
path = root_path + "vendor"
|
|
40
50
|
path += product if product
|
|
41
51
|
path.expand_path
|
|
42
52
|
end
|
|
43
53
|
|
|
54
|
+
# The Busser root, where plugins, suites and vendored products live.
|
|
55
|
+
#
|
|
56
|
+
# @return [Pathname] BUSSER_ROOT if set, otherwise /opt/busser
|
|
44
57
|
def root_path
|
|
45
58
|
Pathname.new(ENV["BUSSER_ROOT"] || "/opt/busser")
|
|
46
59
|
end
|
|
47
60
|
|
|
61
|
+
# No longer supported. Kept so a plugin still calling it warns rather
|
|
62
|
+
# than dying with a NoMethodError halfway through a test run.
|
|
63
|
+
#
|
|
64
|
+
# @param config [Hash] ignored
|
|
65
|
+
# @return [void]
|
|
66
|
+
# @deprecated Shell out or use a Thor action instead.
|
|
48
67
|
def chef_apply(config = {}, &block)
|
|
49
68
|
warn "Apologies, but Busser no longer supports the chef_apply helper," +
|
|
50
69
|
" so the contents of this block will not be executed. Please refactor" +
|
|
@@ -52,6 +71,11 @@ module Busser
|
|
|
52
71
|
" strategy"
|
|
53
72
|
end
|
|
54
73
|
|
|
74
|
+
# Installs a gem into the Busser root's gem home.
|
|
75
|
+
#
|
|
76
|
+
# @param gem [String] the gem name
|
|
77
|
+
# @param version [String, nil] a requirement string, or nil for any version
|
|
78
|
+
# @return [Gem::Specification, nil] the spec that was installed
|
|
55
79
|
def install_gem(gem, version = nil)
|
|
56
80
|
Busser::RubyGems.install_gem(gem, version)
|
|
57
81
|
end
|
data/lib/busser/plugin.rb
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
module Busser
|
|
19
19
|
|
|
20
|
+
# Namespace that runner plugins define their classes inside.
|
|
20
21
|
module RunnerPlugin
|
|
21
22
|
end
|
|
22
23
|
|
|
@@ -28,10 +29,17 @@ module Busser
|
|
|
28
29
|
|
|
29
30
|
module_function
|
|
30
31
|
|
|
32
|
+
# @param plugin_name [String] short plugin name, such as "bash"
|
|
33
|
+
# @return [String] the path to require for that plugin
|
|
31
34
|
def runner_plugin(plugin_name)
|
|
32
35
|
"busser/runner_plugin/#{plugin_name}"
|
|
33
36
|
end
|
|
34
37
|
|
|
38
|
+
# Require paths for the named plugins, or for every installed plugin.
|
|
39
|
+
#
|
|
40
|
+
# @param plugin_names [Array<String>, String, nil] plugin names, or nil for
|
|
41
|
+
# everything installed
|
|
42
|
+
# @return [Array<String>] require paths, without duplicates
|
|
35
43
|
def runner_plugins(plugin_names = nil)
|
|
36
44
|
if plugin_names
|
|
37
45
|
Array(plugin_names).map { |plugin| runner_plugin(plugin) }.uniq
|
|
@@ -46,33 +54,67 @@ module Busser
|
|
|
46
54
|
# duplicates made `busser test` run a suite twice and `busser plugin list`
|
|
47
55
|
# print it twice. The path is what gets required, and require resolves to
|
|
48
56
|
# the active version, so collapsing the repeats is safe.
|
|
57
|
+
# Require paths for every runner plugin installed on this machine.
|
|
58
|
+
#
|
|
59
|
+
# @return [Array<String>] require paths, without duplicates
|
|
49
60
|
def all_runner_plugins
|
|
50
61
|
Gem.find_files("busser/runner_plugin/*.rb").map do |file|
|
|
51
62
|
"busser/runner_plugin/#{File.basename(file).sub(/\.rb$/, "")}"
|
|
52
63
|
end.uniq
|
|
53
64
|
end
|
|
54
65
|
|
|
66
|
+
# Requires a plugin, exiting with a readable message rather than a
|
|
67
|
+
# backtrace if it cannot be loaded.
|
|
68
|
+
#
|
|
69
|
+
# @param plugin_path [String] the path to require
|
|
70
|
+
# @return [void]
|
|
55
71
|
def require!(plugin_path)
|
|
56
72
|
require plugin_path
|
|
57
73
|
rescue LoadError => e
|
|
58
74
|
Busser::UI.die "Could not load #{plugin_path} (#{e.class}: #{e.message})"
|
|
59
75
|
end
|
|
60
76
|
|
|
77
|
+
# @param klass [String, Symbol] a constant name inside
|
|
78
|
+
# {Busser::RunnerPlugin}
|
|
79
|
+
# @return [Class] the runner plugin class
|
|
61
80
|
def runner_class(klass)
|
|
62
81
|
Busser::RunnerPlugin.const_get(klass)
|
|
63
82
|
end
|
|
64
83
|
|
|
84
|
+
# Finds the gemspec a plugin was loaded from.
|
|
85
|
+
#
|
|
86
|
+
# A plugin being developed locally is not an installed gem, so the local
|
|
87
|
+
# gemspec is preferred when the plugin resolves inside the working tree.
|
|
88
|
+
#
|
|
89
|
+
# @param plugin_path [String] the plugin's require path
|
|
90
|
+
# @return [Gem::Specification] the spec the plugin belongs to
|
|
65
91
|
def gem_from_path(plugin_path)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
92
|
+
# Ask RubyGems first. Loading a gemspec *evaluates* it, and these
|
|
93
|
+
# gemspecs shell out to `git ls-files` to build their file list -- so
|
|
94
|
+
# asking the working tree first printed "fatal: not a git repository"
|
|
95
|
+
# into the middle of `busser plugin list` for every installed plugin.
|
|
96
|
+
# find_by_path answers from the installed specs without running anything.
|
|
97
|
+
Gem::Specification.find_by_path(plugin_path) ||
|
|
98
|
+
local_gemspec_for(plugin_path)
|
|
99
|
+
end
|
|
70
100
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
101
|
+
# Falls back to a gemspec in the working tree, which is how a plugin being
|
|
102
|
+
# developed locally -- and so not installed as a gem -- is resolved.
|
|
103
|
+
#
|
|
104
|
+
# @param plugin_path [String] the plugin's require path
|
|
105
|
+
# @return [Gem::Specification, nil] the spec, or nil if there is no local
|
|
106
|
+
# gemspec to read
|
|
107
|
+
def local_gemspec_for(plugin_path)
|
|
108
|
+
root = $LOAD_PATH.first
|
|
109
|
+
return nil if root.nil?
|
|
110
|
+
|
|
111
|
+
local_gem_path = File.expand_path(plugin_path, root)
|
|
112
|
+
return nil if Dir.glob("#{local_gem_path}#{Gem.suffix_pattern}").empty?
|
|
113
|
+
|
|
114
|
+
local_gemspec = File.join(File.dirname(root), "busser.gemspec")
|
|
115
|
+
return nil unless File.exist?(local_gemspec)
|
|
116
|
+
|
|
117
|
+
Gem::Specification.load(local_gemspec)
|
|
76
118
|
end
|
|
77
119
|
end
|
|
78
120
|
end
|
data/lib/busser/rubygems.rb
CHANGED
|
@@ -27,11 +27,24 @@ module Busser
|
|
|
27
27
|
|
|
28
28
|
module_function
|
|
29
29
|
|
|
30
|
+
# @param name [String] the gem name
|
|
31
|
+
# @param version [String, nil] a requirement string, or nil for any version
|
|
32
|
+
# @return [Boolean] true if a matching gem is already available
|
|
30
33
|
def gem_installed?(name, version)
|
|
31
34
|
version = Gem::Requirement.default unless version
|
|
32
35
|
! Gem::Dependency.new(name, version).matching_specs.empty?
|
|
33
36
|
end
|
|
34
37
|
|
|
38
|
+
# Installs a gem into GEM_HOME.
|
|
39
|
+
#
|
|
40
|
+
# RubyGems under bundler points Gem.dir at the bundle, which is not where
|
|
41
|
+
# Busser plugins belong, so GEM_HOME is applied explicitly before the
|
|
42
|
+
# install and the freshly installed spec is put on the load path by hand.
|
|
43
|
+
#
|
|
44
|
+
# @param gem_name [String] the gem name
|
|
45
|
+
# @param version [String, nil] a requirement string, or nil for any version
|
|
46
|
+
# @return [Gem::Specification, nil] the installed spec, or nil if the gem
|
|
47
|
+
# was not among those installed
|
|
35
48
|
def install_gem(gem_name, version)
|
|
36
49
|
version = Gem::Requirement.default unless version
|
|
37
50
|
|
|
@@ -54,6 +67,8 @@ module Busser
|
|
|
54
67
|
spec
|
|
55
68
|
end
|
|
56
69
|
|
|
70
|
+
# @return [Hash] options handed to RubyGems' dependency installer, with
|
|
71
|
+
# the install directory pinned to GEM_HOME
|
|
57
72
|
def rbg_options
|
|
58
73
|
@rbg_options ||= Gem::DependencyInstaller::DEFAULT_OPTIONS.merge(
|
|
59
74
|
suggest_alternate: false,
|
|
@@ -67,6 +82,11 @@ module Busser
|
|
|
67
82
|
)
|
|
68
83
|
end
|
|
69
84
|
|
|
85
|
+
# Runs a block with RubyGems' own progress output suppressed, unless the
|
|
86
|
+
# user asked for verbose output.
|
|
87
|
+
#
|
|
88
|
+
# @yield the block to run quietly
|
|
89
|
+
# @return [Object] whatever the block returned
|
|
70
90
|
def silence_gem_ui
|
|
71
91
|
interaction = Gem::DefaultUserInteraction.ui
|
|
72
92
|
unless Gem.configuration.really_verbose
|
|
@@ -34,6 +34,9 @@ class Busser::RunnerPlugin::Dummy < Busser::RunnerPlugin::Base
|
|
|
34
34
|
create_file("#{dummy_path}/foobar.txt", "The Dummy Driver.")
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
+
# Prints a line so Busser's own suite can prove a plugin ran.
|
|
38
|
+
#
|
|
39
|
+
# @return [void]
|
|
37
40
|
def test
|
|
38
41
|
banner "[dummy] Running"
|
|
39
42
|
if File.exist?(File.join(suite_path("dummy"), "foobar.txt"))
|
data/lib/busser/runner_plugin.rb
CHANGED
|
@@ -27,6 +27,11 @@ module Busser
|
|
|
27
27
|
#
|
|
28
28
|
class Base < Busser::Thor::BaseGroup
|
|
29
29
|
|
|
30
|
+
# Declares work to run once, when Busser installs this plugin -- which
|
|
31
|
+
# is where a plugin installs the test framework it drives.
|
|
32
|
+
#
|
|
33
|
+
# @yield the postinstall body, evaluated on the plugin instance
|
|
34
|
+
# @return [void]
|
|
30
35
|
def self.postinstall(&block)
|
|
31
36
|
(class << self; self; end).send(:define_method, :run_postinstall) do
|
|
32
37
|
klass = Class.new(Busser::Thor::BaseGroup) do
|
data/lib/busser/thor.rb
CHANGED
|
@@ -22,6 +22,7 @@ require "busser/ui"
|
|
|
22
22
|
|
|
23
23
|
module Busser
|
|
24
24
|
|
|
25
|
+
# Thor base classes preloaded with Busser's helpers and output methods.
|
|
25
26
|
module Thor
|
|
26
27
|
|
|
27
28
|
# Base class for all Thor subclasses which includes useful mixins.
|
|
@@ -33,6 +34,17 @@ module Busser
|
|
|
33
34
|
include Helpers
|
|
34
35
|
include UI
|
|
35
36
|
include ::Thor::Actions
|
|
37
|
+
|
|
38
|
+
# Thor exits 0 when a command fails unless this is true, and warns on
|
|
39
|
+
# every failure that it is not set. Busser is a test runner: a failure
|
|
40
|
+
# has to be a non-zero exit or CI reports a red suite as green. Thor 2
|
|
41
|
+
# makes this the default; setting it now silences the deprecation on
|
|
42
|
+
# every failing run and pins the behaviour we already rely on.
|
|
43
|
+
#
|
|
44
|
+
# @return [true]
|
|
45
|
+
def self.exit_on_failure?
|
|
46
|
+
true
|
|
47
|
+
end
|
|
36
48
|
end
|
|
37
49
|
|
|
38
50
|
# Base class for all Thor Group subclasses which includes useful mixins.
|
|
@@ -44,6 +56,17 @@ module Busser
|
|
|
44
56
|
include Helpers
|
|
45
57
|
include UI
|
|
46
58
|
include ::Thor::Actions
|
|
59
|
+
|
|
60
|
+
# Thor exits 0 when a command fails unless this is true, and warns on
|
|
61
|
+
# every failure that it is not set. Busser is a test runner: a failure
|
|
62
|
+
# has to be a non-zero exit or CI reports a red suite as green. Thor 2
|
|
63
|
+
# makes this the default; setting it now silences the deprecation on
|
|
64
|
+
# every failing run and pins the behaviour we already rely on.
|
|
65
|
+
#
|
|
66
|
+
# @return [true]
|
|
67
|
+
def self.exit_on_failure?
|
|
68
|
+
true
|
|
69
|
+
end
|
|
47
70
|
end
|
|
48
71
|
end
|
|
49
72
|
end
|
data/lib/busser/ui.rb
CHANGED
|
@@ -29,30 +29,56 @@ module Busser
|
|
|
29
29
|
|
|
30
30
|
# Thor::Shell's delegated methods are not mixed into this module, so
|
|
31
31
|
# provide the two output primitives it relies on.
|
|
32
|
+
# @param msg [String] text to write to stdout
|
|
33
|
+
# @return [void]
|
|
32
34
|
def say(msg)
|
|
33
35
|
$stdout.puts(msg)
|
|
34
36
|
end
|
|
35
37
|
|
|
38
|
+
# @param msg [String] text to write to stderr
|
|
39
|
+
# @return [void]
|
|
36
40
|
def error(msg)
|
|
37
41
|
$stderr.puts(msg)
|
|
38
42
|
end
|
|
39
43
|
|
|
44
|
+
# Announces a step, at the top level of the output.
|
|
45
|
+
#
|
|
46
|
+
# @param msg [String] the message
|
|
47
|
+
# @return [void]
|
|
40
48
|
def banner(msg)
|
|
41
49
|
say("-----> #{msg}")
|
|
42
50
|
end
|
|
43
51
|
|
|
52
|
+
# Reports detail beneath a banner.
|
|
53
|
+
#
|
|
54
|
+
# @param msg [String] the message
|
|
55
|
+
# @return [void]
|
|
44
56
|
def info(msg)
|
|
45
57
|
say(" #{msg}")
|
|
46
58
|
end
|
|
47
59
|
|
|
60
|
+
# Reports something the user should notice but which is not fatal.
|
|
61
|
+
#
|
|
62
|
+
# @param msg [String] the message
|
|
63
|
+
# @return [void]
|
|
48
64
|
def warn(msg)
|
|
49
65
|
say(">>>>>> #{msg}")
|
|
50
66
|
end
|
|
51
67
|
|
|
68
|
+
# Reports a failure, on stderr.
|
|
69
|
+
#
|
|
70
|
+
# @param msg [String] the message
|
|
71
|
+
# @return [void]
|
|
52
72
|
def fatal(msg)
|
|
53
73
|
error("!!!!!! #{msg}")
|
|
54
74
|
end
|
|
55
75
|
|
|
76
|
+
# Runs a shell command, exiting with a diagnosis if it fails.
|
|
77
|
+
#
|
|
78
|
+
# @param cmd [String] the command line
|
|
79
|
+
# @param config [Hash] options passed through to Thor's runner
|
|
80
|
+
# @return [true] if the command succeeded
|
|
81
|
+
# @see #handle_command
|
|
56
82
|
def run!(cmd, config = {})
|
|
57
83
|
config = { capture: false, verbose: false }.merge(config)
|
|
58
84
|
|
|
@@ -61,6 +87,13 @@ module Busser
|
|
|
61
87
|
end
|
|
62
88
|
end
|
|
63
89
|
|
|
90
|
+
# Runs a Ruby script with the current interpreter, exiting with a
|
|
91
|
+
# diagnosis if it fails.
|
|
92
|
+
#
|
|
93
|
+
# @param cmd [String] the script and its arguments
|
|
94
|
+
# @param config [Hash] options passed through to Thor's runner
|
|
95
|
+
# @return [true] if the script succeeded
|
|
96
|
+
# @see #handle_command
|
|
64
97
|
def run_ruby_script!(cmd, config = {})
|
|
65
98
|
config = { capture: false, verbose: false }.merge(config)
|
|
66
99
|
|
|
@@ -69,15 +102,37 @@ module Busser
|
|
|
69
102
|
end
|
|
70
103
|
end
|
|
71
104
|
|
|
105
|
+
# Reports a failure and exits.
|
|
106
|
+
#
|
|
107
|
+
# @param msg [String] the message
|
|
108
|
+
# @param exitstatus [Integer] the status to exit with
|
|
109
|
+
# @return [void] does not return
|
|
72
110
|
def die(msg, exitstatus = 1)
|
|
73
111
|
fatal(msg)
|
|
74
112
|
exit(exitstatus)
|
|
75
113
|
end
|
|
76
114
|
|
|
115
|
+
# Wrapped so tests can stub it.
|
|
116
|
+
#
|
|
117
|
+
# @return [Process::Status, nil] the status of the last child process
|
|
77
118
|
def status
|
|
78
119
|
$?
|
|
79
120
|
end
|
|
80
121
|
|
|
122
|
+
# Runs a block and turns however the child process ended into a readable
|
|
123
|
+
# message and a matching exit status.
|
|
124
|
+
#
|
|
125
|
+
# Three endings are distinguished because they mean different things to
|
|
126
|
+
# someone reading CI output: a nil status usually means the machine ran out
|
|
127
|
+
# of memory before the child could report; a signal means something killed
|
|
128
|
+
# the run, typically the out of memory killer; and an exit code means the
|
|
129
|
+
# command itself failed.
|
|
130
|
+
#
|
|
131
|
+
# @param type [String] what was run, for the message
|
|
132
|
+
# @param cmd [String] the command line, for the message
|
|
133
|
+
# @yield runs the command
|
|
134
|
+
# @return [true] if the command succeeded
|
|
135
|
+
# @raise [StandardError] re-raised if the block itself raised
|
|
81
136
|
def handle_command(type, cmd)
|
|
82
137
|
begin
|
|
83
138
|
yield
|
|
@@ -96,6 +151,14 @@ module Busser
|
|
|
96
151
|
)
|
|
97
152
|
elsif status.success?
|
|
98
153
|
true
|
|
154
|
+
elsif status.exitstatus.nil?
|
|
155
|
+
# A process killed by a signal has no exit status, and Kernel#exit
|
|
156
|
+
# rejects nil, so reporting it as an exit code raised a TypeError and
|
|
157
|
+
# took Busser down with a stack trace rather than a diagnosis. Signals
|
|
158
|
+
# are how the out of memory killer stops a test run, which is the same
|
|
159
|
+
# situation the nil status branch above was written for.
|
|
160
|
+
signal = status.termsig
|
|
161
|
+
die("#{type} [#{cmd}] was terminated by signal #{signal}", 128 + signal)
|
|
99
162
|
else
|
|
100
163
|
code = status.exitstatus
|
|
101
164
|
die("#{type} [#{cmd}] exit code was #{code}", code)
|
data/lib/busser/version.rb
CHANGED
data/release-please-config.json
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
"changelog-path": "CHANGELOG.md",
|
|
8
8
|
"release-type": "ruby",
|
|
9
9
|
"include-component-in-tag": false,
|
|
10
|
-
"version-file": "lib/busser/version.rb"
|
|
10
|
+
"version-file": "lib/busser/version.rb",
|
|
11
|
+
"bump-minor-pre-major": true,
|
|
12
|
+
"bump-patch-for-minor-pre-major": true
|
|
11
13
|
}
|
|
12
14
|
}
|
|
13
15
|
}
|