gemfile-audit 1.0.0 → 1.1.0
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/CHANGELOG.md +9 -0
- data/README.md +23 -3
- data/lib/gemfile_audit/cli.rb +50 -5
- data/lib/gemfile_audit/collector.rb +11 -5
- data/lib/gemfile_audit/detector.rb +61 -0
- data/lib/gemfile_audit/version.rb +1 -1
- data/lib/gemfile_audit.rb +1 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: df17a16b96f971f02e069cebfb9ea8fe2112256ab8fecb0000907eb7e11db7cd
|
|
4
|
+
data.tar.gz: f9278dc5f11d9bec700f87909145c9a6b7a861d7dc72537893c97ed1658dc82f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2f715ac5c44fbbe37ee25321c4ba45463596a52754ef1ba11bdd6a6cfe9975516b5aad3d4653bccc261e0aeb4d75b46503a555ee22792907b0da3d7c5ad15ce9
|
|
7
|
+
data.tar.gz: 221ab17fb04dece85213a296bfbc06a2c6dbaf4439e4e1a0c52b1dfc01b9e97657e8e82849d1e3309c494836ac59a90487dfc5710a5bbc9d7319a68b07eb15a4
|
data/CHANGELOG.md
ADDED
data/README.md
CHANGED
|
@@ -21,6 +21,24 @@ gemfile-audit measures your Rails test run while it happens: how much of your co
|
|
|
21
21
|
- secrets;
|
|
22
22
|
- database contents.
|
|
23
23
|
|
|
24
|
+
## Quick start
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
gemfile-audit run
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`gemfile-audit run` finds your tests itself. It looks for RSpec, Minitest and Cucumber in the current folder, runs each one it finds and uploads one measurement at the end. If you run your tests another way, give the command yourself:
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
gemfile-audit run -- <command>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
When it finds more than one suite, it runs each in turn, in the order RSpec, Minitest, Cucumber, inside one measured run. The exit status is the first failing status. If a command is stopped by a signal or cannot be started, that status is used instead, the remaining suites are not run and nothing is uploaded.
|
|
37
|
+
|
|
38
|
+
If it cannot find any tests it says so and exits with status 2, so a build never passes without running tests. If a run finishes with no tests counted, it prints "no tests ran" and, when it found a different command, suggests it.
|
|
39
|
+
|
|
40
|
+
In CI, use the explicit form shown in the examples below.
|
|
41
|
+
|
|
24
42
|
## GitHub Actions
|
|
25
43
|
|
|
26
44
|
Add these steps to your workflow. They must come after the step that installs Ruby:
|
|
@@ -37,6 +55,8 @@ Add these steps to your workflow. They must come after the step that installs Ru
|
|
|
37
55
|
|
|
38
56
|
The extra steps never fail your build.
|
|
39
57
|
|
|
58
|
+
The CI examples below use `gemfile-audit run -- bundle exec rspec`. If the gem is missing, they run the same command directly, so your tests still run. Use your project's own test command in both places.
|
|
59
|
+
|
|
40
60
|
## GitLab
|
|
41
61
|
|
|
42
62
|
Put these lines after Ruby is available in the job:
|
|
@@ -84,7 +104,7 @@ Merge request and pull request pipelines are not uploaded. A run there prints "n
|
|
|
84
104
|
|
|
85
105
|
```sh
|
|
86
106
|
gem install gemfile-audit
|
|
87
|
-
gemfile-audit run
|
|
107
|
+
gemfile-audit run
|
|
88
108
|
```
|
|
89
109
|
|
|
90
110
|
Run it from your app folder. The preview is labelled as a local run.
|
|
@@ -94,14 +114,14 @@ Run it from your app folder. The preview is labelled as a local run.
|
|
|
94
114
|
Add `--dry-run` to print the upload instead of sending it:
|
|
95
115
|
|
|
96
116
|
```sh
|
|
97
|
-
gemfile-audit run --dry-run
|
|
117
|
+
gemfile-audit run --dry-run
|
|
98
118
|
```
|
|
99
119
|
|
|
100
120
|
## Requirements
|
|
101
121
|
|
|
102
122
|
- Ruby 2.3 or newer.
|
|
103
123
|
- Rails 4.2 or newer.
|
|
104
|
-
-
|
|
124
|
+
- RSpec, Minitest or Cucumber tests, or one command that runs them.
|
|
105
125
|
|
|
106
126
|
## Privacy
|
|
107
127
|
|
data/lib/gemfile_audit/cli.rb
CHANGED
|
@@ -18,7 +18,8 @@ module GemfileAudit
|
|
|
18
18
|
Usage:
|
|
19
19
|
gemfile-audit start Prepare the environment. In a shell, run: eval "$(gemfile-audit start)"
|
|
20
20
|
gemfile-audit finish [--dry-run] Collect the run and upload it
|
|
21
|
-
gemfile-audit run [--dry-run]
|
|
21
|
+
gemfile-audit run [--dry-run] Find your tests (RSpec, Minitest, Cucumber), run, measure and upload
|
|
22
|
+
gemfile-audit run [--dry-run] -- <command> Run your own test command instead
|
|
22
23
|
gemfile-audit --version Print the version
|
|
23
24
|
gemfile-audit help Print this text
|
|
24
25
|
|
|
@@ -138,7 +139,7 @@ module GemfileAudit
|
|
|
138
139
|
nil
|
|
139
140
|
end
|
|
140
141
|
|
|
141
|
-
def finish_run(env, dry_run)
|
|
142
|
+
def finish_run(env, dry_run, hint: nil)
|
|
142
143
|
dir = env["GEMFILE_AUDIT_DIR"].to_s
|
|
143
144
|
root = env["GEMFILE_AUDIT_ROOT"].to_s
|
|
144
145
|
if dir.empty? || root.empty?
|
|
@@ -156,6 +157,8 @@ module GemfileAudit
|
|
|
156
157
|
return 0
|
|
157
158
|
end
|
|
158
159
|
upload = Collector.new(dir: dir, root: root, ci: ci, env: env).upload
|
|
160
|
+
hint_line = hint && hint.call(upload["tests"])
|
|
161
|
+
@err.puts(hint_line) if hint_line
|
|
159
162
|
if dry_run
|
|
160
163
|
@out.puts(JSON.pretty_generate(upload))
|
|
161
164
|
return 0
|
|
@@ -214,22 +217,64 @@ module GemfileAudit
|
|
|
214
217
|
|
|
215
218
|
def run(args)
|
|
216
219
|
options, command = split_run(args)
|
|
217
|
-
return usage_error("run needs a command, for example: gemfile-audit run -- bundle exec rspec") if command.empty?
|
|
218
220
|
dry_run = options.include?("--dry-run")
|
|
219
221
|
ignore_unknown(options - ["--dry-run"])
|
|
220
222
|
|
|
223
|
+
explicit = !command.empty?
|
|
224
|
+
detected = Detector.new(root: @dir, env: @env).commands
|
|
225
|
+
if explicit
|
|
226
|
+
commands = [command]
|
|
227
|
+
else
|
|
228
|
+
if detected.empty?
|
|
229
|
+
@err.puts("gemfile-audit: could not find your tests. Run them through gemfile-audit run -- <your test command>.")
|
|
230
|
+
return 2
|
|
231
|
+
end
|
|
232
|
+
commands = detected.map { |text| Shellwords.split(text) }
|
|
233
|
+
detected.each { |text| @err.puts("gemfile-audit: would run #{text}") } if dry_run
|
|
234
|
+
end
|
|
235
|
+
|
|
221
236
|
environment = begin
|
|
222
237
|
@env.merge(audit_environment)
|
|
223
238
|
rescue StandardError, ScriptError => error
|
|
224
239
|
warn_line("could not prepare the run: #{sentence(error.message)} Your build is not affected.")
|
|
225
240
|
nil
|
|
226
241
|
end
|
|
227
|
-
status
|
|
228
|
-
|
|
242
|
+
status = 0
|
|
243
|
+
measured = false
|
|
244
|
+
stopped = false
|
|
245
|
+
commands.each do |each_command|
|
|
246
|
+
@err.puts("gemfile-audit: running #{each_command.join(' ')}") unless explicit
|
|
247
|
+
code, ran = execute(environment || @env, each_command)
|
|
248
|
+
# A signal or a missing command stops the run, so it wins over an
|
|
249
|
+
# earlier ordinary failure.
|
|
250
|
+
status = code if (status == 0 || !ran) && code != 0
|
|
251
|
+
measured ||= ran
|
|
252
|
+
# A signal or a command that cannot start ends the run: no more
|
|
253
|
+
# commands and no upload.
|
|
254
|
+
next if ran
|
|
255
|
+
stopped = true
|
|
256
|
+
break
|
|
257
|
+
end
|
|
258
|
+
measured = false if stopped
|
|
259
|
+
hint = no_tests_hint(explicit ? command.join(" ") : nil, detected)
|
|
260
|
+
finish_run(environment, dry_run, hint: hint) if environment && measured
|
|
229
261
|
clear(environment["GEMFILE_AUDIT_DIR"]) if environment && !measured
|
|
230
262
|
status
|
|
231
263
|
end
|
|
232
264
|
|
|
265
|
+
# A hint for after the run. It never changes the upload or the status.
|
|
266
|
+
def no_tests_hint(given, detected)
|
|
267
|
+
lambda do |tests|
|
|
268
|
+
next nil unless (tests && tests["run"] == 0) || (tests.nil? && given)
|
|
269
|
+
suggestion = detected.reject { |text| text.split(" ").first(2) == given.to_s.split(" ").first(2) }
|
|
270
|
+
if given && !suggestion.empty?
|
|
271
|
+
"gemfile-audit: no tests ran. Did you mean #{suggestion.join(' and ')}?"
|
|
272
|
+
else
|
|
273
|
+
"gemfile-audit: no tests ran."
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
233
278
|
# Everything after -- is the command. Without --, the command starts at
|
|
234
279
|
# the first argument that is not an option.
|
|
235
280
|
def split_run(args)
|
|
@@ -43,14 +43,20 @@ module GemfileAudit
|
|
|
43
43
|
}
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
+
# The Gemfile.lock next to BUNDLE_GEMFILE, else the one in the root.
|
|
47
|
+
def self.lockfile_path(root, env)
|
|
48
|
+
root = File.expand_path(root.to_s)
|
|
49
|
+
candidates = []
|
|
50
|
+
gemfile = env["BUNDLE_GEMFILE"]
|
|
51
|
+
candidates << File.join(File.dirname(File.expand_path(gemfile, root)), "Gemfile.lock") if gemfile && !gemfile.empty?
|
|
52
|
+
candidates << File.join(root, "Gemfile.lock")
|
|
53
|
+
candidates.find { |candidate| File.file?(candidate) }
|
|
54
|
+
end
|
|
55
|
+
|
|
46
56
|
private
|
|
47
57
|
|
|
48
58
|
def read_lockfile
|
|
49
|
-
|
|
50
|
-
gemfile = @env["BUNDLE_GEMFILE"]
|
|
51
|
-
candidates << File.join(File.dirname(File.expand_path(gemfile, @root)), "Gemfile.lock") if gemfile && !gemfile.empty?
|
|
52
|
-
candidates << File.join(@root, "Gemfile.lock")
|
|
53
|
-
path = candidates.find { |candidate| File.file?(candidate) }
|
|
59
|
+
path = self.class.lockfile_path(@root, @env)
|
|
54
60
|
raise Missing, "No Gemfile.lock found in #{@root}." unless path
|
|
55
61
|
text = File.read(path, mode: "rb").force_encoding(Encoding::UTF_8)
|
|
56
62
|
text = text.scrub("?") unless text.valid_encoding?
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module GemfileAudit
|
|
2
|
+
# Finds the test commands of a project from its lockfile and folders. It
|
|
3
|
+
# only looks at file names and never fails: anything unreadable counts as
|
|
4
|
+
# not found.
|
|
5
|
+
class Detector
|
|
6
|
+
def initialize(root:, env: ENV)
|
|
7
|
+
@root = File.expand_path(root.to_s)
|
|
8
|
+
@env = env
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def commands
|
|
12
|
+
found = []
|
|
13
|
+
found << "bundle exec rspec" if rspec?
|
|
14
|
+
found << minitest_command if minitest?
|
|
15
|
+
found << "bundle exec cucumber" if cucumber?
|
|
16
|
+
found
|
|
17
|
+
rescue StandardError
|
|
18
|
+
[]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def rspec?
|
|
24
|
+
locked?("rspec-core") && File.directory?(File.join(@root, "spec"))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def cucumber?
|
|
28
|
+
locked?("cucumber") && File.directory?(File.join(@root, "features"))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# A test helper is enough. Otherwise a test file counts when it is not a
|
|
32
|
+
# system test, a fixture or inside node_modules, and Minitest is locked.
|
|
33
|
+
def minitest?
|
|
34
|
+
return true if File.file?(File.join(@root, "test", "test_helper.rb"))
|
|
35
|
+
return false unless locked?("minitest") || locked?("railties")
|
|
36
|
+
Dir.glob(File.join(@root, "test", "**", "*_test.rb")).any? do |path|
|
|
37
|
+
relative = path[(@root.size + 1)..-1]
|
|
38
|
+
!relative.start_with?("test/fixtures/", "test/system/") && !relative.split("/").include?("node_modules")
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def minitest_command
|
|
43
|
+
rails = File.file?(File.join(@root, "bin", "rails")) && locked?("railties")
|
|
44
|
+
rails ? "bin/rails test" : "bundle exec rake test"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def locked?(name)
|
|
48
|
+
!(lockfile =~ /^ {4}#{Regexp.escape(name)} \(/).nil?
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def lockfile
|
|
52
|
+
@lockfile ||= begin
|
|
53
|
+
path = Collector.lockfile_path(@root, @env)
|
|
54
|
+
text = path ? File.read(path, mode: "rb").force_encoding(Encoding::UTF_8) : ""
|
|
55
|
+
text.valid_encoding? ? text : text.scrub("?")
|
|
56
|
+
rescue StandardError
|
|
57
|
+
""
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
data/lib/gemfile_audit.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gemfile-audit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Despo Pentara
|
|
@@ -60,6 +60,7 @@ executables:
|
|
|
60
60
|
extensions: []
|
|
61
61
|
extra_rdoc_files: []
|
|
62
62
|
files:
|
|
63
|
+
- CHANGELOG.md
|
|
63
64
|
- LICENSE
|
|
64
65
|
- README.md
|
|
65
66
|
- exe/gemfile-audit
|
|
@@ -67,6 +68,7 @@ files:
|
|
|
67
68
|
- lib/gemfile_audit/ci.rb
|
|
68
69
|
- lib/gemfile_audit/cli.rb
|
|
69
70
|
- lib/gemfile_audit/collector.rb
|
|
71
|
+
- lib/gemfile_audit/detector.rb
|
|
70
72
|
- lib/gemfile_audit/loader.rb
|
|
71
73
|
- lib/gemfile_audit/measurement.rb
|
|
72
74
|
- lib/gemfile_audit/shell.rb
|