tldr 0.4.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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.