tldr 0.4.0 → 0.6.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: 91190a0094836b21df8aaeccb57684848ada68707a4c52241ebd74437fb514c4
4
- data.tar.gz: a6cc8f1573bdb317f6fd5f337a9670bbeef95f735bdc1003774f4bca0a7b294b
3
+ metadata.gz: 4be82f4551acdf8d4312b512a1c12d2a857cb1d1420b4eebc6d7a74632eecd19
4
+ data.tar.gz: 8f10cecb4d4701fb24f84c36ec78f6b91fe4b381bcf6c86f02f2743f93b61a1f
5
5
  SHA512:
6
- metadata.gz: 93b9fdf0d61dcaba8c3c6dff464700ca260bbdef433eaf799e33130baf5ef63d0e7dec1c6dc3558e5d0f1bc580b53fbacc31c466da1f4a9803533470bb1b5ec5
7
- data.tar.gz: f2a8de04ed3e9b84c277db21f111177849138c1b1b692a43d96492b0707c0e9a8c255c26a9991d899ed27312a04ebe13887a6db7d4417a4878b4e2cfc542acc4
6
+ metadata.gz: 8262b1e6ee4fd4154d9c46d883d1d49b76026a3752b5d26a4ea520fa8b0c0b786019cb3c770b319bd22130c1f194883d02cefbb38b9f171a4571b91ab1793d5f
7
+ data.tar.gz: 125b89ee409f0080a61ceb00232464c455213223883ce10502742a90a825c0a208ed625e5608ecd585a26a900bc49dd8d6a1b49202a88684386c455d9eb313a0
data/CHANGELOG.md CHANGED
@@ -1,4 +1,20 @@
1
- ## [Unreleased]
1
+ ## [0.6.0]
2
+
3
+ * When `dont_run_these_in_parallel!` and `run_these_together!` are called from a
4
+ super class, gather subclasses' methods as well when the method is `nil`
5
+ * Stop shelling out to `tldr` from our Rake task. Rescue `SystemExit` instead
6
+ * Rename `Config#helper` to `Config#helper_paths`, which YAML config keys
7
+ * Print Ruby warnings by default (disable with --no-warnings)
8
+
9
+ ## [0.5.0]
10
+
11
+ * Define your own Rake tasks with `TLDR::Task` and pass in a custom configuration
12
+ * Any tests with `--prepend` AND marked thread-unsafe with `dont_run_these_in_parallel`
13
+ will be run BEFORE the parallel tests start running. This way if you're working
14
+ on a non-parallelizable test, running `tldr` will automatically run it first
15
+ thing
16
+ * Stop printing `--seed` in run commands, since it can be confusing to discover
17
+ that will disable `--parallel`. Instead, print the seed option beneath
2
18
 
3
19
  ## [0.4.0]
4
20
 
data/README.md CHANGED
@@ -23,8 +23,8 @@ suite.
23
23
 
24
24
  Some stuff you might like:
25
25
 
26
- * A CLI that can run tests by line number(s) (e.g. `foo.rb:5 bar.rb:3:10`) and
27
- by names or patterns (e.g. `--name test_fail,test_error --name "/_\d/"`)
26
+ * A CLI that can specify tests by line number(s) (e.g. `foo.rb:5 bar.rb:3:10`)
27
+ and by names or patterns (e.g. `--name test_fail,test_error --name "/_\d/"`)
28
28
  * Everything is **parallel by default**, and seems pretty darn fast; TLDR
29
29
  also provides [several escape hatches to sequester tests that aren't thread-safe](#parallel-by-default-is-nice-in-theory-but-half-my-tests-are-failing-wat)
30
30
  * Surprisingly delightful color diff output when two things fail to equal one
@@ -112,8 +112,8 @@ Usage: tldr [options] some_tests/**/*.rb some/path.rb:13 ...
112
112
  -n, --name PATTERN One or more names or /patterns/ of tests to run (like: foo_test, /test_foo.*/, Foo#foo_test)
113
113
  --exclude-name PATTERN One or more names or /patterns/ NOT to run
114
114
  --exclude-path PATH One or more paths NOT to run (like: foo.rb, "test/bar/**", baz.rb:3)
115
- --helper HELPER Path to a test helper to load before any tests (Default: "test/helper.rb")
116
- --no-helper Don't try loading a test helper before the tests
115
+ --helper PATH One or more paths to a helper that is required before any tests (Default: "test/helper.rb")
116
+ --no-helper Don't require any test helpers
117
117
  --prepend PATH Prepend one or more paths to run before the rest (Default: most recently modified test)
118
118
  --no-prepend Don't prepend any tests before the rest of the suite
119
119
  -l, --load-path PATH Add one or more paths to the $LOAD_PATH (Default: ["test"])
@@ -122,6 +122,7 @@ Usage: tldr [options] some_tests/**/*.rb some/path.rb:13 ...
122
122
  --no-dotfile Disable loading .tldr.yml dotfile
123
123
  --no-emoji Disable emoji in the output
124
124
  -v, --verbose Print stack traces for errors
125
+ --[no-]warnings Print Ruby warnings (Default: true)
125
126
  --comment COMMENT No-op comment, used internally for multi-line execution instructions
126
127
  ```
127
128
 
@@ -181,6 +182,27 @@ You could then run the task with:
181
182
  $ TLDR_OPTS="--no-parallel" bundle exec rake tldr
182
183
  ```
183
184
 
185
+ One reason you'd want to invoke TLDR with Rake is because you have multiple
186
+ test suites that you want to be able to conveniently run separately ([this
187
+ talk](https://blog.testdouble.com/talks/2014-05-25-breaking-up-with-your-test-suite/)
188
+ discussed a few reasons why this can be useful).
189
+
190
+ To create a custom TLDR Rake test, just instantiate `TLDR::Task` like this:
191
+
192
+ ```ruby
193
+ require "tldr/rake"
194
+
195
+ TLDR::Task.new(name: :safe_tests, config: TLDR::Config.new(
196
+ paths: FileList["safe/**/*_test.rb"],
197
+ helper_paths: ["safe/helper.rb"],
198
+ load_paths: ["lib", "safe"]
199
+ ))
200
+ ```
201
+
202
+ The above will create a second Rake task named `safe_tests` running a different
203
+ set of tests than the default `tldr` task. Here's [an
204
+ example](/example/b/Rakefile).
205
+
184
206
  ### Running tests without the CLI
185
207
 
186
208
  If you'd rather use TLDR by running Ruby files instead of the `tldr` CLI
@@ -207,7 +229,7 @@ $ ruby test/some_test.rb
207
229
 
208
230
  To maximize control and to avoid running code accidentally (and _unlike_ the
209
231
  `tldr` CLI), running `at_exit!` will not set default values to the `paths`,
210
- `helper`, `load_paths`, and `prepend_tests` config properties. You'll have to
232
+ `helper`, `load_paths`, and `prepend_paths` config properties. You'll have to
211
233
  pass any values you want to set on a [Config object](/lib/tldr/value/config.rb)
212
234
  and pass it to `at_exit!`.
213
235
 
@@ -4,11 +4,7 @@ class TLDR
4
4
  class ArgvParser
5
5
  PATTERN_FRIENDLY_SPLITTER = /,(?=(?:[^\/]*\/[^\/]*\/)*[^\/]*$)/
6
6
 
7
- def parse(args)
8
- options = {
9
- cli_mode: true
10
- }
11
-
7
+ def parse(args, options = {cli_defaults: true})
12
8
  OptionParser.new do |opts|
13
9
  opts.banner = "Usage: tldr [options] some_tests/**/*.rb some/path.rb:13 ..."
14
10
 
@@ -39,17 +35,18 @@ class TLDR
39
35
  options[:exclude_paths] += path
40
36
  end
41
37
 
42
- opts.on "#{CONFLAGS[:helper]} HELPER", String, "Path to a test helper to load before any tests (Default: \"test/helper.rb\")" do |helper|
43
- options[:helper] = helper
38
+ opts.on "#{CONFLAGS[:helper_paths]} PATH", Array, "One or more paths to a helper that is required before any tests (Default: \"test/helper.rb\")" do |path|
39
+ options[:helper_paths] ||= []
40
+ options[:helper_paths] += path
44
41
  end
45
42
 
46
- opts.on CONFLAGS[:no_helper], "Don't try loading a test helper before the tests" do
43
+ opts.on CONFLAGS[:no_helper], "Don't require any test helpers" do
47
44
  options[:no_helper] = true
48
45
  end
49
46
 
50
- opts.on "#{CONFLAGS[:prepend_tests]} PATH", Array, "Prepend one or more paths to run before the rest (Default: most recently modified test)" do |prepend|
51
- options[:prepend_tests] ||= []
52
- options[:prepend_tests] += prepend
47
+ opts.on "#{CONFLAGS[:prepend_paths]} PATH", Array, "Prepend one or more paths to run before the rest (Default: most recently modified test)" do |prepend|
48
+ options[:prepend_paths] ||= []
49
+ options[:prepend_paths] += prepend
53
50
  end
54
51
 
55
52
  opts.on CONFLAGS[:no_prepend], "Don't prepend any tests before the rest of the suite" do
@@ -81,6 +78,10 @@ class TLDR
81
78
  options[:verbose] = verbose
82
79
  end
83
80
 
81
+ opts.on CONFLAGS[:warnings], "Print Ruby warnings (Default: true)" do |warnings|
82
+ options[:warnings] = warnings
83
+ end
84
+
84
85
  opts.on "--comment COMMENT", String, "No-op comment, used internally for multi-line execution instructions" do
85
86
  # See "--comment" in lib/tldr/reporters/default.rb for an example of how this is used internally
86
87
  end
@@ -0,0 +1,15 @@
1
+ class TLDR
2
+ module ClassUtil
3
+ def self.gather_descendants root_klass
4
+ root_klass.subclasses + root_klass.subclasses.flat_map { |subklass|
5
+ gather_descendants subklass
6
+ }
7
+ end
8
+
9
+ def self.gather_tests klass
10
+ klass.instance_methods.grep(/^test_/).sort.map { |method|
11
+ Test.new klass, method
12
+ }
13
+ end
14
+ end
15
+ end
@@ -8,12 +8,18 @@ class TLDR
8
8
  )
9
9
  end
10
10
 
11
- def parallelize all_tests, parallel, &blk
12
- return run_in_sequence(all_tests, &blk) if all_tests.size < 2 || !parallel
11
+ def parallelize all_tests, config, &blk
12
+ return run_in_sequence(all_tests, &blk) if all_tests.size < 2 || !config.parallel
13
13
 
14
- strategy = @strategizer.strategize all_tests, GROUPED_TESTS, THREAD_UNSAFE_TESTS
14
+ strategy = @strategizer.strategize(
15
+ all_tests,
16
+ GROUPED_TESTS,
17
+ THREAD_UNSAFE_TESTS,
18
+ (config.no_prepend ? [] : config.prepend_paths)
19
+ )
15
20
 
16
- run_in_parallel(strategy.parallel_tests_and_groups, &blk) +
21
+ run_in_sequence(strategy.prepend_thread_unsafe_tests, &blk) +
22
+ run_in_parallel(strategy.parallel_tests_and_groups, &blk) +
17
23
  run_in_sequence(strategy.thread_unsafe_tests, &blk)
18
24
  end
19
25
 
@@ -0,0 +1,43 @@
1
+ class TLDR
2
+ module PathUtil
3
+ def self.expand_paths path_strings, globs: true
4
+ path_strings = expand_globs path_strings if globs
5
+
6
+ path_strings.flat_map { |path_string|
7
+ File.directory?(path_string) ? Dir["#{path_string}/**/*.rb"] : path_string
8
+ }.flat_map { |path_string|
9
+ absolute_path = File.expand_path(path_string.gsub(/:.*$/, ""), Dir.pwd)
10
+ line_numbers = path_string.scan(/:(\d+)/).flatten.map(&:to_i)
11
+
12
+ if line_numbers.any?
13
+ line_numbers.map { |line_number| Location.new absolute_path, line_number }
14
+ else
15
+ [Location.new(absolute_path, nil)]
16
+ end
17
+ }.uniq
18
+ end
19
+
20
+ # Because search paths to TLDR can include line numbers (e.g. a.rb:4), we
21
+ # can't just pass everything to Dir.glob. Instead, we have to check whether
22
+ # a user-provided search path looks like a glob, and if so, expand it
23
+ #
24
+ # Globby characters specified here:
25
+ # https://ruby-doc.org/3.2.2/Dir.html#method-c-glob
26
+ def self.expand_globs search_paths
27
+ search_paths.flat_map { |search_path|
28
+ if search_path.match?(/[*?\[\]{}]/)
29
+ raise Error, "Can't combine globs and line numbers in: #{search_path}" if search_path.match?(/:(\d+)$/)
30
+ Dir[search_path]
31
+ else
32
+ search_path
33
+ end
34
+ }
35
+ end
36
+
37
+ def self.locations_include_test? locations, test
38
+ locations.any? { |location|
39
+ location.file == test.file && (location.line.nil? || test.covers_line?(location.line))
40
+ }
41
+ end
42
+ end
43
+ end
data/lib/tldr/planner.rb CHANGED
@@ -3,7 +3,8 @@ require "pathname"
3
3
  class TLDR
4
4
  class Planner
5
5
  def plan config
6
- search_locations = expand_search_locations config.paths
6
+ $VERBOSE = config.warnings
7
+ search_locations = PathUtil.expand_paths config.paths, globs: false
7
8
 
8
9
  prepend_load_paths config
9
10
  require_test_helper config
@@ -32,34 +33,17 @@ class TLDR
32
33
 
33
34
  private
34
35
 
35
- def expand_search_locations path_strings
36
- path_strings.flat_map { |path_string|
37
- File.directory?(path_string) ? Dir["#{path_string}/**/*.rb"] : path_string
38
- }.flat_map { |path_string|
39
- absolute_path = File.expand_path(path_string.gsub(/:.*$/, ""), Dir.pwd)
40
- line_numbers = path_string.scan(/:(\d+)/).flatten.map(&:to_i)
41
-
42
- if line_numbers.any?
43
- line_numbers.map { |line_number| Location.new absolute_path, line_number }
44
- else
45
- [Location.new(absolute_path, nil)]
46
- end
47
- }.uniq
48
- end
49
-
50
36
  def gather_tests
51
- gather_descendants(TLDR).flat_map { |subklass|
52
- subklass.instance_methods.grep(/^test_/).sort.map { |method|
53
- Test.new subklass, method
54
- }
37
+ ClassUtil.gather_descendants(TLDR).flat_map { |subklass|
38
+ ClassUtil.gather_tests(subklass)
55
39
  }
56
40
  end
57
41
 
58
42
  def prepend tests, config
59
43
  return tests if config.no_prepend
60
- prepended_locations = expand_search_locations expand_globs config.prepend_tests
44
+ prepended_locations = PathUtil.expand_paths config.prepend_paths
61
45
  prepended, rest = tests.partition { |test|
62
- locations_include_test? prepended_locations, test
46
+ PathUtil.locations_include_test? prepended_locations, test
63
47
  }
64
48
  prepended + rest
65
49
  end
@@ -69,11 +53,11 @@ class TLDR
69
53
  end
70
54
 
71
55
  def exclude_by_path tests, exclude_paths
72
- excluded_locations = expand_search_locations expand_globs exclude_paths
56
+ excluded_locations = PathUtil.expand_paths exclude_paths
73
57
  return tests if excluded_locations.empty?
74
58
 
75
59
  tests.reject { |test|
76
- locations_include_test? excluded_locations, test
60
+ PathUtil.locations_include_test? excluded_locations, test
77
61
  }
78
62
  end
79
63
 
@@ -94,7 +78,7 @@ class TLDR
94
78
  return tests if line_specific_locations.empty?
95
79
 
96
80
  tests.select { |test|
97
- locations_include_test? line_specific_locations, test
81
+ PathUtil.locations_include_test? line_specific_locations, test
98
82
  }
99
83
  end
100
84
 
@@ -117,8 +101,12 @@ class TLDR
117
101
  end
118
102
 
119
103
  def require_test_helper config
120
- return if config.no_helper || config.helper.nil? || !File.exist?(config.helper)
121
- require File.expand_path(config.helper, Dir.pwd)
104
+ return if config.no_helper || config.helper_paths.empty?
105
+ PathUtil.expand_paths(config.helper_paths).map(&:file).uniq.each do |helper_file|
106
+ next unless File.exist?(helper_file)
107
+
108
+ require helper_file
109
+ end
122
110
  end
123
111
 
124
112
  def require_tests search_locations
@@ -127,35 +115,6 @@ class TLDR
127
115
  end
128
116
  end
129
117
 
130
- def gather_descendants root_klass
131
- root_klass.subclasses + root_klass.subclasses.flat_map { |subklass|
132
- gather_descendants subklass
133
- }
134
- end
135
-
136
- def locations_include_test? locations, test
137
- locations.any? { |location|
138
- location.file == test.file && (location.line.nil? || test.covers_line?(location.line))
139
- }
140
- end
141
-
142
- # Because search paths to TLDR can include line numbers (e.g. a.rb:4), we
143
- # can't just pass everything to Dir.glob. Instead, we have to check whether
144
- # a user-provided search path looks like a glob, and if so, expand it
145
- #
146
- # Globby characters specified here:
147
- # https://ruby-doc.org/3.2.2/Dir.html#method-c-glob
148
- def expand_globs search_paths
149
- search_paths.flat_map { |search_path|
150
- if search_path.match?(/[*?\[\]{}]/)
151
- raise Error, "Can't combine globs and line numbers in: #{search_path}" if search_path.match?(/:(\d+)$/)
152
- Dir[search_path]
153
- else
154
- search_path
155
- end
156
- }
157
- end
158
-
159
118
  def expand_names_with_patterns names
160
119
  names.map { |name|
161
120
  if name.is_a?(String) && name =~ /^\/(.*)\/$/
data/lib/tldr/rake.rb CHANGED
@@ -1,4 +1,43 @@
1
- desc "Run tests with TLDR (use TLDR_OPTS or .tldr.yml to configure)"
2
- task :tldr do
3
- fail unless system "#{"bundle exec " if defined?(Bundler)}tldr #{ENV["TLDR_OPTS"]}"
1
+ require "rake"
2
+ require "shellwords"
3
+
4
+ require "tldr"
5
+
6
+ class TLDR
7
+ class Task
8
+ include Rake::DSL
9
+
10
+ def initialize(name: "tldr", config: Config.new)
11
+ define name, config
12
+ end
13
+
14
+ private
15
+
16
+ def define name, task_config
17
+ desc "Run #{name} tests (use TLDR_OPTS or .tldr.yml to configure)"
18
+ task name do
19
+ argv = Shellwords.shellwords(merge_env_opts(task_config).to_full_args)
20
+ begin
21
+ TLDR::Run.cli(argv)
22
+ rescue SystemExit => e
23
+ fail "TLDR task #{name} failed with status #{e.status}" unless e.status == 0
24
+ end
25
+ end
26
+ end
27
+
28
+ def merge_env_opts task_config
29
+ if ENV["TLDR_OPTS"]
30
+ env_argv = Shellwords.shellwords(ENV["TLDR_OPTS"])
31
+ opts_config = ArgvParser.new.parse(env_argv, {
32
+ config_intended_for_merge_only: true
33
+ })
34
+ task_config.merge(opts_config)
35
+ else
36
+ task_config
37
+ end
38
+ end
39
+ end
4
40
  end
41
+
42
+ # Create the default tldr task for users
43
+ TLDR::Task.new
@@ -10,6 +10,7 @@ class TLDR
10
10
  @suite_start_time = Process.clock_gettime Process::CLOCK_MONOTONIC, :microsecond
11
11
  @out.print <<~MSG
12
12
  Command: #{tldr_command} #{@config.to_full_args}
13
+ #{@icons.seed} #{CONFLAGS[:seed]} #{@config.seed}
13
14
 
14
15
  #{@icons.run} Running:
15
16
 
@@ -43,6 +43,10 @@ module IconProvider
43
43
  def rock_on
44
44
  ""
45
45
  end
46
+
47
+ def seed
48
+ ""
49
+ end
46
50
  end
47
51
 
48
52
  class Emoji < Base
@@ -89,5 +93,9 @@ module IconProvider
89
93
  def rock_on
90
94
  "🤘"
91
95
  end
96
+
97
+ def seed
98
+ "🌱"
99
+ end
92
100
  end
93
101
  end
data/lib/tldr/runner.rb CHANGED
@@ -34,7 +34,7 @@ class TLDR
34
34
  end
35
35
  }
36
36
 
37
- results = @parallelizer.parallelize(plan.tests, config.parallel) { |test|
37
+ results = @parallelizer.parallelize(plan.tests, config) { |test|
38
38
  e = nil
39
39
  start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
40
40
  wip_test = WIPTest.new test, start_time
@@ -1,6 +1,6 @@
1
1
  class TLDR
2
2
  class Strategizer
3
- Strategy = Struct.new :parallel_tests_and_groups, :thread_unsafe_tests
3
+ Strategy = Struct.new :prepend_thread_unsafe_tests, :parallel_tests_and_groups, :thread_unsafe_tests
4
4
 
5
5
  # Combine all discovered test methods with any methods grouped by run_these_together!
6
6
  #
@@ -8,13 +8,13 @@ class TLDR
8
8
  # - Map over tests to build out groups in order to retain shuffle order
9
9
  # (group will run in position of first test in the group)
10
10
  # - If a test is in multiple groups, only run it once
11
- def strategize all_tests, run_these_together_groups, thread_unsafe_test_groups
11
+ def strategize all_tests, run_these_together_groups, thread_unsafe_test_groups, prepend_paths
12
12
  thread_unsafe_tests, thread_safe_tests = partition_unsafe(all_tests, thread_unsafe_test_groups)
13
+ prepend_thread_unsafe_tests, thread_unsafe_tests = partition_prepend(thread_unsafe_tests, prepend_paths)
13
14
 
14
15
  grouped_tests = prepare_run_together_groups run_these_together_groups, thread_safe_tests, thread_unsafe_tests
15
16
  already_included_groups = []
16
-
17
- Strategy.new thread_safe_tests.map { |test|
17
+ Strategy.new prepend_thread_unsafe_tests, thread_safe_tests.map { |test|
18
18
  if (group = grouped_tests.find { |group| group.tests.include? test })
19
19
  if already_included_groups.include? group
20
20
  next
@@ -39,6 +39,16 @@ class TLDR
39
39
  }
40
40
  end
41
41
 
42
+ # Sadly duplicative with Planner.rb, necessitating the extraction of PathUtil
43
+ # Suboptimal, but we do indeed need to do this work in two places ¯\_(ツ)_/¯
44
+ def partition_prepend thread_unsafe_tests, prepend_paths
45
+ locations = PathUtil.expand_paths prepend_paths
46
+
47
+ thread_unsafe_tests.partition { |test|
48
+ PathUtil.locations_include_test? locations, test
49
+ }
50
+ end
51
+
42
52
  def prepare_run_together_groups run_these_together_groups, thread_safe_tests, thread_unsafe_tests
43
53
  grouped_tests = run_these_together_groups.map(&:dup)
44
54
 
@@ -4,40 +4,53 @@ class TLDR
4
4
  no_helper: "--no-helper",
5
5
  verbose: "--verbose",
6
6
  reporter: "--reporter",
7
- helper: "--helper",
7
+ helper_paths: "--helper",
8
8
  load_paths: "--load-path",
9
9
  parallel: "--[no-]parallel",
10
10
  names: "--name",
11
11
  fail_fast: "--fail-fast",
12
12
  no_emoji: "--no-emoji",
13
- prepend_tests: "--prepend",
13
+ prepend_paths: "--prepend",
14
14
  no_prepend: "--no-prepend",
15
15
  exclude_paths: "--exclude-path",
16
16
  exclude_names: "--exclude-name",
17
17
  base_path: "--base-path",
18
18
  no_dotfile: "--no-dotfile",
19
+ warnings: "--[no-]warnings",
19
20
  paths: nil
20
21
  }.freeze
21
22
 
22
- PATH_FLAGS = [:paths, :helper, :load_paths, :prepend_tests, :exclude_paths].freeze
23
+ PATH_FLAGS = [:paths, :helper_paths, :load_paths, :prepend_paths, :exclude_paths].freeze
23
24
  MOST_RECENTLY_MODIFIED_TAG = "MOST_RECENTLY_MODIFIED".freeze
24
-
25
- Config = Struct.new :paths, :seed, :no_helper, :verbose, :reporter,
26
- :helper, :load_paths, :parallel, :names, :fail_fast, :no_emoji,
27
- :prepend_tests, :no_prepend, :exclude_paths, :exclude_names, :base_path,
28
- :no_dotfile,
29
- :seed_set_intentionally, :cli_mode, keyword_init: true do
25
+ CONFIG_ATTRIBUTES = [
26
+ :paths, :seed, :no_helper, :verbose, :reporter,
27
+ :helper_paths, :load_paths, :parallel, :names, :fail_fast, :no_emoji,
28
+ :prepend_paths, :no_prepend, :exclude_paths, :exclude_names, :base_path,
29
+ :no_dotfile, :warnings,
30
+ # Internal properties
31
+ :config_intended_for_merge_only, :seed_set_intentionally, :cli_defaults
32
+ ].freeze
33
+
34
+ Config = Struct.new(*CONFIG_ATTRIBUTES, keyword_init: true) do
30
35
  def initialize(**args)
31
- change_working_directory_because_i_am_bad_and_i_should_feel_bad!(args[:base_path])
32
- args = merge_dotfile_args(args) unless args[:no_dotfile]
36
+ unless args[:config_intended_for_merge_only]
37
+ change_working_directory_because_i_am_bad_and_i_should_feel_bad!(args[:base_path])
38
+ args = merge_dotfile_args(args) unless args[:no_dotfile]
39
+ end
40
+ args = undefault_parallel_if_seed_set(args)
41
+ unless args[:config_intended_for_merge_only]
42
+ args = merge_defaults(args)
43
+ end
33
44
 
34
- super(**merge_defaults(args))
45
+ super(**args)
35
46
  end
36
47
 
37
- # Must be set when the Config is first initialized
38
- undef_method :cli_mode=, :no_dotfile=, :base_path=
48
+ # These are for internal tracking and resolved at initialization-time
49
+ undef_method :config_intended_for_merge_only=, :seed_set_intentionally=,
50
+ # These must be set when the Config is first initialized
51
+ :cli_defaults=, :no_dotfile=, :base_path=
39
52
 
40
- def self.build_defaults(cli_mode = false)
53
+ def self.build_defaults cli_defaults: true
41
54
  common = {
42
55
  seed: rand(10_000),
43
56
  no_helper: false,
@@ -50,62 +63,71 @@ class TLDR
50
63
  no_prepend: false,
51
64
  exclude_paths: [],
52
65
  exclude_names: [],
53
- base_path: nil
66
+ base_path: nil,
67
+ warnings: true
54
68
  }
55
69
 
56
- if cli_mode
70
+ if cli_defaults
57
71
  common.merge(
58
72
  paths: Dir["test/**/*_test.rb", "test/**/test_*.rb"],
59
- helper: "test/helper.rb",
73
+ helper_paths: ["test/helper.rb"],
60
74
  load_paths: ["test"],
61
- prepend_tests: [MOST_RECENTLY_MODIFIED_TAG]
75
+ prepend_paths: [MOST_RECENTLY_MODIFIED_TAG]
62
76
  )
63
77
  else
64
78
  common.merge(
65
79
  paths: [],
66
- helper: nil,
80
+ helper_paths: [],
67
81
  load_paths: [],
68
- prepend_tests: []
82
+ prepend_paths: []
69
83
  )
70
84
  end
71
85
  end
72
86
 
73
- def merge_defaults(user_args)
74
- merged_args = user_args.dup
75
- defaults = Config.build_defaults(merged_args[:cli_mode])
76
- merged_args[:seed_set_intentionally] = !merged_args[:seed].nil?
87
+ def undefault_parallel_if_seed_set args
88
+ args.merge(
89
+ seed_set_intentionally: !args[:seed].nil?,
90
+ parallel: (args[:parallel].nil? ? args[:seed].nil? : args[:parallel])
91
+ )
92
+ end
77
93
 
78
- # Special cases
79
- if merged_args[:parallel].nil?
80
- # Disable parallelization if seed is set
81
- merged_args[:parallel] = merged_args[:seed].nil?
82
- end
94
+ def merge_defaults user_args
95
+ merged_args = user_args.dup
96
+ defaults = Config.build_defaults(cli_defaults: merged_args[:cli_defaults])
83
97
 
84
98
  # Arrays
85
- [:paths, :load_paths, :names, :prepend_tests, :exclude_paths, :exclude_names].each do |key|
99
+ [:paths, :helper_paths, :load_paths, :names, :prepend_paths, :exclude_paths, :exclude_names].each do |key|
86
100
  merged_args[key] = defaults[key] if merged_args[key].nil? || merged_args[key].empty?
87
101
  end
88
102
 
89
103
  # Booleans
90
- [:no_helper, :verbose, :fail_fast, :no_emoji, :no_prepend].each do |key|
104
+ [:no_helper, :verbose, :fail_fast, :no_emoji, :no_prepend, :warnings].each do |key|
91
105
  merged_args[key] = defaults[key] if merged_args[key].nil?
92
106
  end
93
107
 
94
108
  # Values
95
- [:seed, :reporter, :helper].each do |key|
109
+ [:seed, :reporter].each do |key|
96
110
  merged_args[key] ||= defaults[key]
97
111
  end
98
112
 
99
113
  merged_args
100
114
  end
101
115
 
116
+ def merge other
117
+ this_config = to_h
118
+ kwargs = this_config.merge(
119
+ other.to_h.compact.except(:config_intended_for_merge_only)
120
+ )
121
+ Config.new(**kwargs)
122
+ end
123
+
102
124
  # We needed this hook (to be called by the planner), because we can't know
103
125
  # the default prepend location until we have all the resolved test paths,
104
126
  # so we have to mutate it after the fact.
105
127
  def update_after_gathering_tests! tests
106
- return unless prepend_tests.include?(MOST_RECENTLY_MODIFIED_TAG)
128
+ return unless prepend_paths.include?(MOST_RECENTLY_MODIFIED_TAG)
107
129
 
108
- self.prepend_tests = prepend_tests.map { |path|
130
+ self.prepend_paths = prepend_paths.map { |path|
109
131
  if path == MOST_RECENTLY_MODIFIED_TAG
110
132
  most_recently_modified_test_file tests
111
133
  else
@@ -115,12 +137,17 @@ class TLDR
115
137
  end
116
138
 
117
139
  def to_full_args(exclude: [])
118
- to_cli_argv(CONFLAGS.keys - exclude).join(" ")
140
+ to_cli_argv(
141
+ CONFLAGS.keys -
142
+ exclude - [
143
+ (:seed unless seed_set_intentionally)
144
+ ]
145
+ ).join(" ")
119
146
  end
120
147
 
121
148
  def to_single_path_args(path)
122
149
  argv = to_cli_argv(CONFLAGS.keys - [
123
- :seed, :parallel, :names, :fail_fast, :paths, :prepend_tests,
150
+ :seed, :parallel, :names, :fail_fast, :paths, :prepend_paths,
124
151
  :no_prepend, :exclude_paths
125
152
  ])
126
153
 
@@ -129,21 +156,21 @@ class TLDR
129
156
 
130
157
  private
131
158
 
132
- def to_cli_argv(options = CONFLAGS.keys)
133
- defaults = Config.build_defaults(cli_mode)
159
+ def to_cli_argv options = CONFLAGS.keys
160
+ defaults = Config.build_defaults(cli_defaults: true)
134
161
  options.map { |key|
135
162
  flag = CONFLAGS[key]
136
163
 
137
164
  # Special cases
138
- if key == :prepend_tests
139
- if prepend_tests.map { |s| stringify(key, s) }.sort == paths.map { |s| stringify(:paths, s) }.sort
165
+ if key == :prepend_paths
166
+ if prepend_paths.map { |s| stringify(key, s) }.sort == paths.map { |s| stringify(:paths, s) }.sort
140
167
  # Don't print prepended tests if they're the same as the test paths
141
168
  next
142
169
  elsif no_prepend
143
170
  # Don't print prepended tests if they're disabled
144
171
  next
145
172
  end
146
- elsif key == :helper && no_helper
173
+ elsif key == :helper_paths && no_helper
147
174
  # Don't print the helper if it's disabled
148
175
  next
149
176
  elsif key == :parallel
@@ -153,6 +180,8 @@ class TLDR
153
180
  "--parallel"
154
181
  end
155
182
  next val
183
+ elsif key == :warnings && defaults[:warnings] != self[:warnings]
184
+ next warnings ? "--warnings" : "--no-warnings"
156
185
  end
157
186
 
158
187
  if defaults[key] == self[key]
@@ -208,6 +237,9 @@ class TLDR
208
237
  if dotfile_args.key?(:reporter)
209
238
  dotfile_args[:reporter] = Kernel.const_get(dotfile_args[:reporter])
210
239
  end
240
+ if (invalid_args = dotfile_args.except(*CONFIG_ATTRIBUTES)).any?
241
+ raise Error, "Invalid keys in .tldr.yml file: #{invalid_args.keys.join(", ")}"
242
+ end
211
243
 
212
244
  dotfile_args.merge(args)
213
245
  end
@@ -6,8 +6,8 @@ class TLDR
6
6
  @tests ||= configuration.flat_map { |(klass, method)|
7
7
  klass = Kernel.const_get(klass) if klass.is_a? String
8
8
  if method.nil?
9
- klass.instance_methods.grep(/^test_/).sort.map { |method|
10
- Test.new klass, method
9
+ ([klass] + ClassUtil.gather_descendants(klass)).flat_map { |klass|
10
+ ClassUtil.gather_tests(klass)
11
11
  }
12
12
  else
13
13
  Test.new klass, method
data/lib/tldr/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class TLDR
2
- VERSION = "0.4.0"
2
+ VERSION = "0.6.0"
3
3
  end
data/lib/tldr.rb CHANGED
@@ -3,9 +3,11 @@ require "concurrent-ruby"
3
3
  require_relative "tldr/argv_parser"
4
4
  require_relative "tldr/assertions"
5
5
  require_relative "tldr/backtrace_filter"
6
+ require_relative "tldr/class_util"
6
7
  require_relative "tldr/error"
7
8
  require_relative "tldr/parallel_controls"
8
9
  require_relative "tldr/parallelizer"
10
+ require_relative "tldr/path_util"
9
11
  require_relative "tldr/planner"
10
12
  require_relative "tldr/reporters"
11
13
  require_relative "tldr/runner"
@@ -39,6 +41,8 @@ class TLDR
39
41
  def self.at_exit! config = Config.new
40
42
  # Ignore at_exit when running tldr CLI, since that will run any tests
41
43
  return if $PROGRAM_NAME.end_with? "tldr"
44
+ # Also ignore if we're running from within our rake task
45
+ return if caller.any? { |line| line.include? "lib/tldr/rake.rb" }
42
46
  # Ignore at_exit when we've already registered an at_exit hook
43
47
  return if @@at_exit_registered
44
48
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tldr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Searls
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2023-09-27 00:00:00.000000000 Z
12
+ date: 2023-09-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: super_diff
@@ -59,9 +59,11 @@ files:
59
59
  - lib/tldr/assertions.rb
60
60
  - lib/tldr/assertions/minitest_compatibility.rb
61
61
  - lib/tldr/backtrace_filter.rb
62
+ - lib/tldr/class_util.rb
62
63
  - lib/tldr/error.rb
63
64
  - lib/tldr/parallel_controls.rb
64
65
  - lib/tldr/parallelizer.rb
66
+ - lib/tldr/path_util.rb
65
67
  - lib/tldr/planner.rb
66
68
  - lib/tldr/rake.rb
67
69
  - lib/tldr/reporters.rb
@@ -105,7 +107,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
105
107
  - !ruby/object:Gem::Version
106
108
  version: '0'
107
109
  requirements: []
108
- rubygems_version: 3.4.17
110
+ rubygems_version: 3.4.6
109
111
  signing_key:
110
112
  specification_version: 4
111
113
  summary: TLDR will run your tests, but only for 1.8 seconds.