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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f7075c7b895832615c7a6f71f7e9dcaeb697b6c077a4d1ba6cd6a453c9168f9b
4
- data.tar.gz: 5bb001386eb6f57b5101d2794a675449d17295bdce54a1c81287008b0cba8075
3
+ metadata.gz: df17a16b96f971f02e069cebfb9ea8fe2112256ab8fecb0000907eb7e11db7cd
4
+ data.tar.gz: f9278dc5f11d9bec700f87909145c9a6b7a861d7dc72537893c97ed1658dc82f
5
5
  SHA512:
6
- metadata.gz: fd5c6848b704868311ff14a0d6c9db8d625784f2755d7035c35f47dd96c624efaec585d05eda4ad5f16b81e05c2e0259954973ee54030ff3b85398a7b2eea331
7
- data.tar.gz: 5c1f1be103e1dc761dd7eefd9b0fb01eab94a936582cf97f211fca242a2d1e4fe9f99de1c4dd3eb951519dae17672d29a991e371ad87f0b63550a5009e314381
6
+ metadata.gz: 2f715ac5c44fbbe37ee25321c4ba45463596a52754ef1ba11bdd6a6cfe9975516b5aad3d4653bccc261e0aeb4d75b46503a555ee22792907b0da3d7c5ad15ce9
7
+ data.tar.gz: 221ab17fb04dece85213a296bfbc06a2c6dbaf4439e4e1a0c52b1dfc01b9e97657e8e82849d1e3309c494836ac59a90487dfc5710a5bbc9d7319a68b07eb15a4
data/CHANGELOG.md ADDED
@@ -0,0 +1,9 @@
1
+ # Changelog
2
+
3
+ ## 1.1.0
4
+
5
+ gemfile-audit run finds your tests itself (RSpec, Minitest, Cucumber) and warns when no tests ran.
6
+
7
+ ## 1.0.0
8
+
9
+ First release.
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 -- bundle exec rspec
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 -- bundle exec rspec
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
- - Tests that run with one command.
124
+ - RSpec, Minitest or Cucumber tests, or one command that runs them.
105
125
 
106
126
  ## Privacy
107
127
 
@@ -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] -- <command> Run a command, measure it and upload the 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, measured = execute(environment || @env, command)
228
- finish_run(environment, dry_run) if environment && measured
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
- 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
- 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
@@ -1,3 +1,3 @@
1
1
  module GemfileAudit
2
- VERSION = "1.0.0".freeze
2
+ VERSION = "1.1.0".freeze
3
3
  end
data/lib/gemfile_audit.rb CHANGED
@@ -2,6 +2,7 @@ require "gemfile_audit/version"
2
2
  require "gemfile_audit/shell"
3
3
  require "gemfile_audit/ci"
4
4
  require "gemfile_audit/collector"
5
+ require "gemfile_audit/detector"
5
6
  require "gemfile_audit/uploader"
6
7
  require "gemfile_audit/cli"
7
8
 
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.0.0
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