busser 0.9.1 → 0.9.3
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/integration.yml +55 -0
- data/.github/workflows/publish.yml +22 -0
- data/.gitignore +1 -0
- data/.release-please-manifest.json +1 -1
- data/.rubocop.yml +1 -4
- data/CHANGELOG.md +19 -0
- data/Gemfile +12 -5
- data/README.md +82 -0
- data/Rakefile +19 -1
- data/busser.gemspec +11 -8
- data/kitchen-preinstall.sh +55 -0
- data/kitchen.yml +27 -0
- data/lib/busser/command/deserialize.rb +4 -0
- data/lib/busser/command/plugin_create.rb +50 -0
- data/lib/busser/command/plugin_install.rb +40 -4
- data/lib/busser/command/plugin_list.rb +16 -4
- data/lib/busser/command/setup.rb +27 -1
- 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 +55 -0
- data/lib/busser/version.rb +2 -1
- data/release-please-config.json +3 -1
- data/renovate.json +1 -0
- data/spec/busser/command/deserialize_spec.rb +0 -1
- data/spec/busser/command/plugin_create_spec.rb +79 -7
- 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 +21 -1
- 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 +7 -7
- data/spec/spec_helper.rb +2 -3
- data/templates/plugin/Gemfile.erb +7 -0
- data/templates/plugin/Rakefile.erb +5 -1
- data/templates/plugin/features_env.rb.erb +3 -8
- data/templates/plugin/gemspec.erb +2 -6
- data/templates/plugin/github_workflow.yml.erb +1 -1
- data/test/integration/default/bash/smoke_test.sh +12 -0
- metadata +15 -59
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 22ea0046cc1c5cb2f4473f9315c506ee3ebd4b22d7cf35de392060ae02816ae9
|
|
4
|
+
data.tar.gz: a42365f1541af1364e6eb82d06049a2910fc5fbceb63cf137e6aaad5243f1d64
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4fe454f0801dff2a7dac3677a73f98055bf2368ccaa503a1fb641997cf0210482bfd9ff456f853e4e4a8a7e40c4291e8c67f4f9dfc65ce574596fd8be1123cbd
|
|
7
|
+
data.tar.gz: 3138b112b0ac5a405361553b0a20e9ea43fbbf5df16fbeb6be0b7cb325811e6c494e8d44998af2dc15c6fc163b7568ec681c3a8edd7dddff79018ade13eb46c1
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "Test Kitchen"
|
|
3
|
+
|
|
4
|
+
"on":
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
pull_request:
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
concurrency:
|
|
14
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
15
|
+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
kitchen:
|
|
19
|
+
name: "kitchen verify on Ruby ${{ matrix.ruby }}"
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
strategy:
|
|
22
|
+
fail-fast: false
|
|
23
|
+
matrix:
|
|
24
|
+
# The ends of the supported range. The unit and feature suites already
|
|
25
|
+
# cover everything in between; this job is about the Test Kitchen
|
|
26
|
+
# integration, not Ruby compatibility.
|
|
27
|
+
ruby: ["3.2", "4.0"]
|
|
28
|
+
env:
|
|
29
|
+
# Outside the checkout on purpose. The runners require "bundler/setup",
|
|
30
|
+
# which walks up from the suite looking for a Gemfile -- so a Busser root
|
|
31
|
+
# inside the workspace finds this project's Gemfile and tries to
|
|
32
|
+
# materialize that bundle inside the isolated gem home, which fails. A
|
|
33
|
+
# real Busser root is /opt/busser, never inside the project.
|
|
34
|
+
BUSSER_ROOT: /tmp/busser-kitchen
|
|
35
|
+
steps:
|
|
36
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
37
|
+
|
|
38
|
+
# Deliberately no bundler-cache, and nothing below runs under `bundle
|
|
39
|
+
# exec`. The verifier shells out to `gem list` to decide what to install,
|
|
40
|
+
# and bundler in the environment makes that report the bundle's gems
|
|
41
|
+
# rather than the Busser root's -- so it skips installing busser and the
|
|
42
|
+
# run dies on a path that was never created. Test Kitchen is not run from
|
|
43
|
+
# inside a project's bundle in real use either.
|
|
44
|
+
- uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
|
|
45
|
+
with:
|
|
46
|
+
ruby-version: ${{ matrix.ruby }}
|
|
47
|
+
|
|
48
|
+
- name: Install Test Kitchen
|
|
49
|
+
run: gem install test-kitchen --no-document --version "~> 4.1"
|
|
50
|
+
|
|
51
|
+
- name: Install this working tree into the Busser root
|
|
52
|
+
run: ./kitchen-preinstall.sh
|
|
53
|
+
|
|
54
|
+
- name: kitchen verify
|
|
55
|
+
run: kitchen verify
|
|
@@ -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
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -33,6 +33,25 @@ 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.3](https://github.com/test-kitchen/busser/compare/v0.9.2...v0.9.3) (2026-08-29)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
### Bug Fixes
|
|
40
|
+
|
|
41
|
+
* bring the generated plugin scaffold up to date ([#81](https://github.com/test-kitchen/busser/issues/81)) ([f67b8d0](https://github.com/test-kitchen/busser/commit/f67b8d0979d924b2cb653dace74a80fec03a3da5))
|
|
42
|
+
* reject plugin names that cannot be a Ruby constant ([#80](https://github.com/test-kitchen/busser/issues/80)) ([bd7ac17](https://github.com/test-kitchen/busser/commit/bd7ac1749afe458e35328ebb7b57c64634ad1acc))
|
|
43
|
+
* use the Windows path separator in the bat binstub ([#79](https://github.com/test-kitchen/busser/issues/79)) ([1eb0cbf](https://github.com/test-kitchen/busser/commit/1eb0cbf744ce68fc3c4126bf2eec73f784f422e5))
|
|
44
|
+
|
|
45
|
+
## [0.9.2](https://github.com/test-kitchen/busser/compare/v0.9.1...v0.9.2) (2026-08-28)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
### Bug Fixes
|
|
49
|
+
|
|
50
|
+
* 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))
|
|
51
|
+
* 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))
|
|
52
|
+
* 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))
|
|
53
|
+
* 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))
|
|
54
|
+
|
|
36
55
|
## [0.9.1](https://github.com/test-kitchen/busser/compare/v0.9.0...v0.9.1) (2026-08-28)
|
|
37
56
|
|
|
38
57
|
|
data/Gemfile
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
source "https://rubygems.org"
|
|
2
2
|
|
|
3
|
-
gemspec
|
|
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 "
|
|
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 "
|
|
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", "
|
|
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 =
|
|
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.
|
|
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.
|
|
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
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Installs this working tree into the Busser root the Test Kitchen verifier
|
|
3
|
+
# will use, so `kitchen verify` exercises the code on this branch rather than
|
|
4
|
+
# the last release from RubyGems.
|
|
5
|
+
#
|
|
6
|
+
# The verifier's install script skips its own install when `gem list` already
|
|
7
|
+
# shows what it wants, so putting things in place first is all that is needed.
|
|
8
|
+
# Two consequences to handle:
|
|
9
|
+
#
|
|
10
|
+
# * It also has to install busser itself. That check is `grep "^busser"` with
|
|
11
|
+
# no anchor at the end, so a plugin named busser-* satisfies it and busser
|
|
12
|
+
# would otherwise never be installed at all.
|
|
13
|
+
# * Skipping `busser plugin install` also skips the plugin's postinstall,
|
|
14
|
+
# which is where a plugin installs the test framework it drives. So run the
|
|
15
|
+
# postinstall here, which is what the verifier would have done.
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
|
|
18
|
+
BUSSER_ROOT="${BUSSER_ROOT:-/tmp/busser-kitchen}"
|
|
19
|
+
gemspec="$(ls ./*.gemspec)"
|
|
20
|
+
gem_name="$(basename "${gemspec}" .gemspec)"
|
|
21
|
+
|
|
22
|
+
install_opts=(
|
|
23
|
+
--install-dir "${BUSSER_ROOT}/gems"
|
|
24
|
+
--bindir "${BUSSER_ROOT}/bin"
|
|
25
|
+
--no-document
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
mkdir -p "${BUSSER_ROOT}/gems"
|
|
29
|
+
|
|
30
|
+
gem build "${gemspec}" --output "${BUSSER_ROOT}/${gem_name}.gem" >/dev/null
|
|
31
|
+
|
|
32
|
+
if [ "${gem_name}" = "busser" ]; then
|
|
33
|
+
# No --local: a plugin's runtime dependencies are things its runner needs on
|
|
34
|
+
# the machine under test, and they have to be fetched into the Busser root
|
|
35
|
+
# like everything else.
|
|
36
|
+
gem install "${BUSSER_ROOT}/${gem_name}.gem" "${install_opts[@]}" >/dev/null
|
|
37
|
+
else
|
|
38
|
+
gem install busser "${install_opts[@]}" >/dev/null
|
|
39
|
+
# Not --ignore-dependencies: a plugin's runtime dependencies are things its
|
|
40
|
+
# runner needs on the machine under test, and they have to be in the Busser
|
|
41
|
+
# root like everything else.
|
|
42
|
+
# No --local: a plugin's runtime dependencies are things its runner needs on
|
|
43
|
+
# the machine under test, and they have to be fetched into the Busser root
|
|
44
|
+
# like everything else.
|
|
45
|
+
gem install "${BUSSER_ROOT}/${gem_name}.gem" "${install_opts[@]}" >/dev/null
|
|
46
|
+
|
|
47
|
+
# The same environment the verifier exports, so the framework this plugin
|
|
48
|
+
# installs lands in the Busser root rather than the ambient gem home.
|
|
49
|
+
BUSSER_ROOT="${BUSSER_ROOT}" \
|
|
50
|
+
GEM_HOME="${BUSSER_ROOT}/gems" \
|
|
51
|
+
GEM_PATH="${BUSSER_ROOT}/gems" \
|
|
52
|
+
"${BUSSER_ROOT}/bin/busser" plugin install "${gem_name}" --force-postinstall
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
echo "pre-installed ${gem_name} from the working tree into ${BUSSER_ROOT}"
|
data/kitchen.yml
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
# Runs Busser the way Test Kitchen actually runs it -- real verifier,
|
|
3
|
+
# real busser, real suite transfer -- but against the machine running the tests
|
|
4
|
+
# rather than a VM or container, so it works unchanged in CI.
|
|
5
|
+
driver:
|
|
6
|
+
name: exec
|
|
7
|
+
|
|
8
|
+
provisioner:
|
|
9
|
+
name: dummy
|
|
10
|
+
|
|
11
|
+
verifier:
|
|
12
|
+
name: busser
|
|
13
|
+
# The verifier defaults to Chef's omnibus Ruby, which is not what is running
|
|
14
|
+
# here, and to sudo, which is not needed for a path under the workspace.
|
|
15
|
+
ruby_bindir: <%= RbConfig::CONFIG["bindir"] %>
|
|
16
|
+
sudo: false
|
|
17
|
+
root_path: <%= ENV.fetch("BUSSER_ROOT", "/tmp/busser-kitchen") %>
|
|
18
|
+
|
|
19
|
+
platforms:
|
|
20
|
+
- name: local
|
|
21
|
+
|
|
22
|
+
suites:
|
|
23
|
+
# busser-bash is the exercise: a real plugin, installed by the verifier from
|
|
24
|
+
# RubyGems, driven by the busser built from this working tree. The internal
|
|
25
|
+
# dummy runner would prove the chain runs but can never fail, so it would not
|
|
26
|
+
# notice a regression.
|
|
27
|
+
- name: default
|
|
@@ -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,7 +36,25 @@ 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]
|
|
42
|
+
# A plugin name becomes three things: a require path, a directory name and
|
|
43
|
+
# a Ruby constant. Only the last is fussy, and Thor's camel_case only
|
|
44
|
+
# folds underscores -- so "my-junit" produced `module My-junit`, which is
|
|
45
|
+
# not parseable Ruby. The generator ran to completion and wrote a project
|
|
46
|
+
# that could not be loaded at all.
|
|
47
|
+
NAME_PATTERN = /\A[a-z][a-z0-9_]*\z/
|
|
48
|
+
|
|
39
49
|
def create
|
|
50
|
+
unless NAME_PATTERN.match?(name)
|
|
51
|
+
raise ::Thor::Error,
|
|
52
|
+
"'#{name}' is not a usable plugin name. A name becomes a Ruby " \
|
|
53
|
+
"constant, so it must start with a lowercase letter and contain " \
|
|
54
|
+
"only lowercase letters, digits and underscores. Try " \
|
|
55
|
+
"'#{suggested_name}'."
|
|
56
|
+
end
|
|
57
|
+
|
|
40
58
|
self.class.source_root(Busser.source_root.join("templates", "plugin"))
|
|
41
59
|
|
|
42
60
|
create_core_files
|
|
@@ -47,6 +65,9 @@ module Busser
|
|
|
47
65
|
|
|
48
66
|
private
|
|
49
67
|
|
|
68
|
+
# Writes the gemspec, Gemfile, Rakefile, README and licence.
|
|
69
|
+
#
|
|
70
|
+
# @return [void]
|
|
50
71
|
def create_core_files
|
|
51
72
|
empty_directory(target_dir)
|
|
52
73
|
|
|
@@ -60,6 +81,9 @@ module Busser
|
|
|
60
81
|
create_template("github_workflow.yml.erb", ".github/workflows/test.yml")
|
|
61
82
|
end
|
|
62
83
|
|
|
84
|
+
# Writes lib/, the version file and the runner plugin class.
|
|
85
|
+
#
|
|
86
|
+
# @return [void]
|
|
63
87
|
def create_source_files
|
|
64
88
|
empty_directory(File.join(target_dir, "lib/busser", name))
|
|
65
89
|
empty_directory(File.join(target_dir, "lib/busser/runner_plugin"))
|
|
@@ -74,6 +98,9 @@ module Busser
|
|
|
74
98
|
)
|
|
75
99
|
end
|
|
76
100
|
|
|
101
|
+
# Writes a starter cucumber suite for the new plugin.
|
|
102
|
+
#
|
|
103
|
+
# @return [void]
|
|
77
104
|
def create_features_files
|
|
78
105
|
empty_directory(File.join(target_dir, "features/support"))
|
|
79
106
|
|
|
@@ -95,6 +122,10 @@ module Busser
|
|
|
95
122
|
)
|
|
96
123
|
end
|
|
97
124
|
|
|
125
|
+
# Runs git init in the generated project, so the gemspec's
|
|
126
|
+
# `git ls-files` has something to read.
|
|
127
|
+
#
|
|
128
|
+
# @return [void]
|
|
98
129
|
def initialize_git
|
|
99
130
|
inside(target_dir) do
|
|
100
131
|
run("git init")
|
|
@@ -102,14 +133,27 @@ module Busser
|
|
|
102
133
|
end
|
|
103
134
|
end
|
|
104
135
|
|
|
136
|
+
# @param erb [String] template path, relative to the templates directory
|
|
137
|
+
# @param dest [String] destination path, relative to the target directory
|
|
138
|
+
# @return [void]
|
|
105
139
|
def create_template(erb, dest)
|
|
106
140
|
template(erb, File.join(target_dir, dest), config)
|
|
107
141
|
end
|
|
108
142
|
|
|
143
|
+
# @return [String] directory the new plugin is generated into
|
|
144
|
+
# @return [String] the given name reshaped into something usable, for the
|
|
145
|
+
# error message
|
|
146
|
+
def suggested_name
|
|
147
|
+
name.to_s.downcase.gsub(/[^a-z0-9_]+/, "_").sub(/\A[^a-z]+/, "").squeeze("_")
|
|
148
|
+
end
|
|
149
|
+
|
|
109
150
|
def target_dir
|
|
110
151
|
File.join(Dir.pwd, "busser-#{name}")
|
|
111
152
|
end
|
|
112
153
|
|
|
154
|
+
# Values the templates interpolate.
|
|
155
|
+
#
|
|
156
|
+
# @return [Hash] the template binding
|
|
113
157
|
def config
|
|
114
158
|
@config ||= begin
|
|
115
159
|
type_klass_name = "#{::Thor::Util.camel_case(options[:type])}Plugin"
|
|
@@ -132,16 +176,19 @@ module Busser
|
|
|
132
176
|
end
|
|
133
177
|
end
|
|
134
178
|
|
|
179
|
+
# @return [String] the author name, from git config where available
|
|
135
180
|
def author
|
|
136
181
|
git_user_name = `git config user.name`.chomp
|
|
137
182
|
git_user_name.empty? ? "TODO: Write your name" : git_user_name
|
|
138
183
|
end
|
|
139
184
|
|
|
185
|
+
# @return [String] the author email, from git config where available
|
|
140
186
|
def email
|
|
141
187
|
git_user_email = `git config user.email`.chomp
|
|
142
188
|
git_user_email.empty? ? "TODO: Write your email" : git_user_email
|
|
143
189
|
end
|
|
144
190
|
|
|
191
|
+
# @return [String] full licence text for the chosen licence
|
|
145
192
|
def license_string
|
|
146
193
|
case options[:license]
|
|
147
194
|
when "mit" then "MIT"
|
|
@@ -168,6 +215,8 @@ module Busser
|
|
|
168
215
|
end
|
|
169
216
|
end
|
|
170
217
|
|
|
218
|
+
# @return [String] the filename the licence is written to, which differs
|
|
219
|
+
# by licence
|
|
171
220
|
def license_filename
|
|
172
221
|
case options[:license]
|
|
173
222
|
when "mit" then "LICENSE.txt"
|
|
@@ -178,6 +227,7 @@ module Busser
|
|
|
178
227
|
end
|
|
179
228
|
end
|
|
180
229
|
|
|
230
|
+
# @return [String] the licence header comment prepended to source files
|
|
181
231
|
def license_comment
|
|
182
232
|
@license_comment ||= IO.read(File.join(target_dir, license_filename))
|
|
183
233
|
.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",
|
|
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
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|