tldr 0.5.0 → 0.6.1

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: 32f7b9dbcf9e09433eef762eaafe19804643aa543ac2c0a9599303ea2e960f25
4
- data.tar.gz: 789c07fb2db1fad39b234976999b575a1bea27c4796a4d062a373df11d3716e5
3
+ metadata.gz: 8e0c6abcfc7b58fc6de05591d61ceadfcbbed4867cfb140081f62877bb80d9a6
4
+ data.tar.gz: e62acff5d7e667214527d5bcafef6ba2540abbe4a187f441da07575919a34da0
5
5
  SHA512:
6
- metadata.gz: 7975ec24ae3f080ed564666c5319928964c970794a740a21253551be1be9b945374394b2f860cdd8737151f8e21fdb0d592c126895dd02b91dd21c899824226a
7
- data.tar.gz: b11f3c52624b63a7aa44eadd4f17aa662d134648d61eb55eb7124e7956d0b28f20a04545552f9af8710330ffa98c94e85cec87dc80715a532522ccdeb15a8b42
6
+ metadata.gz: 9c31d1f3fba81d133e2ef570b2252504cc67150d0729e56e0690fe5dbb8e3bd0512bec39e95a178eb12a8108fa92e56365fa981c3fc528ee3c845939237c347f
7
+ data.tar.gz: 3e35999424094dc072b6f2be670259e584dc14edf5621236a37efb0282c38c9ae434d63bd33fbe44b23f8fbf6f1b79fef801fb5f9375ff924d851c8063f63ba8
data/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## [0.6.1]
2
+
3
+ * Correctly report the number of test classes that run
4
+ * Finish planning the test run before starting the clock on the timer (that's
5
+ a millisecond or two in savings!)
6
+
7
+ ## [0.6.0]
8
+
9
+ * When `dont_run_these_in_parallel!` and `run_these_together!` are called from a
10
+ super class, gather subclasses' methods as well when the method is `nil`
11
+ * Stop shelling out to `tldr` from our Rake task. Rescue `SystemExit` instead
12
+ * Rename `Config#helper` to `Config#helper_paths`, which YAML config keys
13
+ * Print Ruby warnings by default (disable with --no-warnings)
14
+
1
15
  ## [0.5.0]
2
16
 
3
17
  * Define your own Rake tasks with `TLDR::Task` and pass in a custom configuration
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
 
@@ -193,7 +194,7 @@ require "tldr/rake"
193
194
 
194
195
  TLDR::Task.new(name: :safe_tests, config: TLDR::Config.new(
195
196
  paths: FileList["safe/**/*_test.rb"],
196
- helper: "safe/helper.rb",
197
+ helper_paths: ["safe/helper.rb"],
197
198
  load_paths: ["lib", "safe"]
198
199
  ))
199
200
  ```
data/Rakefile CHANGED
@@ -6,6 +6,7 @@ require "rake/testtask"
6
6
  Rake::TestTask.new(:test) do |t|
7
7
  t.libs << "tests"
8
8
  t.libs << "lib"
9
+ t.warning = true
9
10
  t.test_files = FileList["tests/**/*_test.rb"]
10
11
  end
11
12
 
@@ -35,11 +35,12 @@ class TLDR
35
35
  options[:exclude_paths] += path
36
36
  end
37
37
 
38
- opts.on "#{CONFLAGS[:helper]} HELPER", String, "Path to a test helper to load before any tests (Default: \"test/helper.rb\")" do |helper|
39
- 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
40
41
  end
41
42
 
42
- 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
43
44
  options[:no_helper] = true
44
45
  end
45
46
 
@@ -77,6 +78,10 @@ class TLDR
77
78
  options[:verbose] = verbose
78
79
  end
79
80
 
81
+ opts.on CONFLAGS[:warnings], "Print Ruby warnings (Default: true)" do |warnings|
82
+ options[:warnings] = warnings
83
+ end
84
+
80
85
  opts.on "--comment COMMENT", String, "No-op comment, used internally for multi-line execution instructions" do
81
86
  # See "--comment" in lib/tldr/reporters/default.rb for an example of how this is used internally
82
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
@@ -1,26 +1,20 @@
1
1
  class TLDR
2
- class Parallelizer
2
+ class Executor
3
3
  def initialize
4
- @strategizer = Strategizer.new
5
4
  @thread_pool = Concurrent::ThreadPoolExecutor.new(
6
5
  name: "tldr",
7
6
  auto_terminate: true
8
7
  )
9
8
  end
10
9
 
11
- def parallelize all_tests, config, &blk
12
- return run_in_sequence(all_tests, &blk) if all_tests.size < 2 || !config.parallel
13
-
14
- strategy = @strategizer.strategize(
15
- all_tests,
16
- GROUPED_TESTS,
17
- THREAD_UNSAFE_TESTS,
18
- (config.no_prepend ? [] : config.prepend_paths)
19
- )
20
-
21
- run_in_sequence(strategy.prepend_thread_unsafe_tests, &blk) +
22
- run_in_parallel(strategy.parallel_tests_and_groups, &blk) +
23
- run_in_sequence(strategy.thread_unsafe_tests, &blk)
10
+ def execute plan, &blk
11
+ if plan.strategy.parallel?
12
+ run_in_sequence(plan.strategy.prepend_sequential_tests, &blk) +
13
+ run_in_parallel(plan.strategy.parallel_tests_and_groups, &blk) +
14
+ run_in_sequence(plan.strategy.append_sequential_tests, &blk)
15
+ else
16
+ run_in_sequence(plan.tests, &blk)
17
+ end
24
18
  end
25
19
 
26
20
  private
@@ -1,6 +1,8 @@
1
1
  class TLDR
2
2
  module PathUtil
3
- def self.expand_search_locations path_strings
3
+ def self.expand_paths path_strings, globs: true
4
+ path_strings = expand_globs path_strings if globs
5
+
4
6
  path_strings.flat_map { |path_string|
5
7
  File.directory?(path_string) ? Dir["#{path_string}/**/*.rb"] : path_string
6
8
  }.flat_map { |path_string|
data/lib/tldr/planner.rb CHANGED
@@ -2,8 +2,13 @@ require "pathname"
2
2
 
3
3
  class TLDR
4
4
  class Planner
5
+ def initialize
6
+ @strategizer = Strategizer.new
7
+ end
8
+
5
9
  def plan config
6
- search_locations = PathUtil.expand_search_locations config.paths
10
+ $VERBOSE = config.warnings
11
+ search_locations = PathUtil.expand_paths config.paths, globs: false
7
12
 
8
13
  prepend_load_paths config
9
14
  require_test_helper config
@@ -11,8 +16,7 @@ class TLDR
11
16
 
12
17
  tests = gather_tests
13
18
  config.update_after_gathering_tests! tests
14
-
15
- Plan.new prepend(
19
+ tests_to_run = prepend(
16
20
  shuffle(
17
21
  exclude_by_path(
18
22
  exclude_by_name(
@@ -28,21 +32,28 @@ class TLDR
28
32
  ),
29
33
  config
30
34
  )
35
+
36
+ strategy = @strategizer.strategize(
37
+ tests_to_run,
38
+ GROUPED_TESTS,
39
+ THREAD_UNSAFE_TESTS,
40
+ config
41
+ )
42
+
43
+ Plan.new tests_to_run, strategy
31
44
  end
32
45
 
33
46
  private
34
47
 
35
48
  def gather_tests
36
- gather_descendants(TLDR).flat_map { |subklass|
37
- subklass.instance_methods.grep(/^test_/).sort.map { |method|
38
- Test.new subklass, method
39
- }
49
+ ClassUtil.gather_descendants(TLDR).flat_map { |subklass|
50
+ ClassUtil.gather_tests(subklass)
40
51
  }
41
52
  end
42
53
 
43
54
  def prepend tests, config
44
55
  return tests if config.no_prepend
45
- prepended_locations = PathUtil.expand_search_locations PathUtil.expand_globs config.prepend_paths
56
+ prepended_locations = PathUtil.expand_paths config.prepend_paths
46
57
  prepended, rest = tests.partition { |test|
47
58
  PathUtil.locations_include_test? prepended_locations, test
48
59
  }
@@ -54,7 +65,7 @@ class TLDR
54
65
  end
55
66
 
56
67
  def exclude_by_path tests, exclude_paths
57
- excluded_locations = PathUtil.expand_search_locations PathUtil.expand_globs exclude_paths
68
+ excluded_locations = PathUtil.expand_paths exclude_paths
58
69
  return tests if excluded_locations.empty?
59
70
 
60
71
  tests.reject { |test|
@@ -69,7 +80,7 @@ class TLDR
69
80
 
70
81
  tests.reject { |test|
71
82
  name_excludes.any? { |filter|
72
- filter === test.method.to_s || filter === "#{test.klass}##{test.method}"
83
+ filter === test.method_name.to_s || filter === "#{test.test_class}##{test.method_name}"
73
84
  }
74
85
  }
75
86
  end
@@ -90,7 +101,7 @@ class TLDR
90
101
 
91
102
  tests.select { |test|
92
103
  name_filters.any? { |filter|
93
- filter === test.method.to_s || filter === "#{test.klass}##{test.method}"
104
+ filter === test.method_name.to_s || filter === "#{test.test_class}##{test.method_name}"
94
105
  }
95
106
  }
96
107
  end
@@ -102,8 +113,12 @@ class TLDR
102
113
  end
103
114
 
104
115
  def require_test_helper config
105
- return if config.no_helper || config.helper.nil? || !File.exist?(config.helper)
106
- require File.expand_path(config.helper, Dir.pwd)
116
+ return if config.no_helper || config.helper_paths.empty?
117
+ PathUtil.expand_paths(config.helper_paths).map(&:file).uniq.each do |helper_file|
118
+ next unless File.exist?(helper_file)
119
+
120
+ require helper_file
121
+ end
107
122
  end
108
123
 
109
124
  def require_tests search_locations
@@ -112,12 +127,6 @@ class TLDR
112
127
  end
113
128
  end
114
129
 
115
- def gather_descendants root_klass
116
- root_klass.subclasses + root_klass.subclasses.flat_map { |subklass|
117
- gather_descendants subklass
118
- }
119
- end
120
-
121
130
  def expand_names_with_patterns names
122
131
  names.map { |name|
123
132
  if name.is_a?(String) && name =~ /^\/(.*)\/$/
data/lib/tldr/rake.rb CHANGED
@@ -16,13 +16,17 @@ class TLDR
16
16
  def define name, task_config
17
17
  desc "Run #{name} tests (use TLDR_OPTS or .tldr.yml to configure)"
18
18
  task name do
19
- cli_args = build_cli_args(task_config)
20
- fail unless system "#{"bundle exec " if defined?(Bundler)}tldr #{cli_args}"
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
21
25
  end
22
26
  end
23
27
 
24
- def build_cli_args task_config
25
- config = if ENV["TLDR_OPTS"]
28
+ def merge_env_opts task_config
29
+ if ENV["TLDR_OPTS"]
26
30
  env_argv = Shellwords.shellwords(ENV["TLDR_OPTS"])
27
31
  opts_config = ArgvParser.new.parse(env_argv, {
28
32
  config_intended_for_merge_only: true
@@ -31,8 +35,6 @@ class TLDR
31
35
  else
32
36
  task_config
33
37
  end
34
-
35
- config.to_full_args
36
38
  end
37
39
  end
38
40
  end
@@ -9,7 +9,8 @@ class TLDR
9
9
  def before_suite tests
10
10
  @suite_start_time = Process.clock_gettime Process::CLOCK_MONOTONIC, :microsecond
11
11
  @out.print <<~MSG
12
- Command: #{tldr_command} #{@config.to_full_args}#{"\n#{@icons.seed} #{CONFLAGS[:seed]} #{@config.seed}" unless @config.seed_set_intentionally}
12
+ Command: #{tldr_command} #{@config.to_full_args}
13
+ #{@icons.seed} #{CONFLAGS[:seed]} #{@config.seed}
13
14
 
14
15
  #{@icons.run} Running:
15
16
 
@@ -86,7 +87,7 @@ class TLDR
86
87
  @out.print "Finished in #{duration}ms."
87
88
 
88
89
  @out.print "\n\n"
89
- class_count = test_results.uniq { |result| result.test.class }.size
90
+ class_count = test_results.uniq { |result| result.test.test_class }.size
90
91
  test_count = test_results.size
91
92
  @out.print [
92
93
  plural(class_count, "test class", "test classes"),
@@ -126,7 +127,7 @@ class TLDR
126
127
  end
127
128
 
128
129
  def describe test, location = test.location
129
- "#{test.klass}##{test.method} [#{location.locator}]"
130
+ "#{test.test_class}##{test.method_name} [#{location.locator}]"
130
131
  end
131
132
 
132
133
  def plural count, singular, plural = "#{singular}s"
data/lib/tldr/runner.rb CHANGED
@@ -3,7 +3,7 @@ require "irb"
3
3
  class TLDR
4
4
  class Runner
5
5
  def initialize
6
- @parallelizer = Parallelizer.new
6
+ @executor = Executor.new
7
7
  @wip = Concurrent::Array.new
8
8
  @results = Concurrent::Array.new
9
9
  @run_aborted = Concurrent::AtomicBoolean.new false
@@ -12,8 +12,8 @@ class TLDR
12
12
  def run config, plan
13
13
  @wip.clear
14
14
  @results.clear
15
- reporter = config.reporter.new config
16
- reporter.before_suite plan.tests
15
+ reporter = config.reporter.new(config)
16
+ reporter.before_suite(plan.tests)
17
17
 
18
18
  time_bomb = Thread.new {
19
19
  explode = proc do
@@ -34,34 +34,8 @@ class TLDR
34
34
  end
35
35
  }
36
36
 
37
- results = @parallelizer.parallelize(plan.tests, config) { |test|
38
- e = nil
39
- start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
40
- wip_test = WIPTest.new test, start_time
41
- @wip << wip_test
42
- runtime = time_it(start_time) do
43
- instance = test.klass.new
44
- instance.setup if instance.respond_to? :setup
45
- if instance.respond_to? :around
46
- did_run = false
47
- instance.around {
48
- did_run = true
49
- instance.send(test.method)
50
- }
51
- raise Error, "#{test.klass}#around failed to yield or call the passed test block" unless did_run
52
- else
53
- instance.send(test.method)
54
- end
55
- instance.teardown if instance.respond_to? :teardown
56
- rescue Skip, Failure, StandardError => e
57
- end
58
- TestResult.new(test, e, runtime).tap do |result|
59
- next if @run_aborted.true?
60
- @results << result
61
- @wip.delete wip_test
62
- reporter.after_test result
63
- fail_fast reporter, plan, result if result.failing? && config.fail_fast
64
- end
37
+ results = @executor.execute(plan) { |test|
38
+ run_test(test, config, plan, reporter)
65
39
  }.tap do
66
40
  time_bomb.kill
67
41
  end
@@ -74,6 +48,36 @@ class TLDR
74
48
 
75
49
  private
76
50
 
51
+ def run_test test, config, plan, reporter
52
+ e = nil
53
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
54
+ wip_test = WIPTest.new test, start_time
55
+ @wip << wip_test
56
+ runtime = time_it(start_time) do
57
+ instance = test.test_class.new
58
+ instance.setup if instance.respond_to? :setup
59
+ if instance.respond_to? :around
60
+ did_run = false
61
+ instance.around {
62
+ did_run = true
63
+ instance.send(test.method_name)
64
+ }
65
+ raise Error, "#{test.test_class}#around failed to yield or call the passed test block" unless did_run
66
+ else
67
+ instance.send(test.method_name)
68
+ end
69
+ instance.teardown if instance.respond_to? :teardown
70
+ rescue Skip, Failure, StandardError => e
71
+ end
72
+ TestResult.new(test, e, runtime).tap do |result|
73
+ next if @run_aborted.true?
74
+ @results << result
75
+ @wip.delete wip_test
76
+ reporter.after_test result
77
+ fail_fast reporter, plan, result if result.failing? && config.fail_fast
78
+ end
79
+ end
80
+
77
81
  def fail_fast reporter, plan, fast_failed_result
78
82
  unless @run_aborted.true?
79
83
  @run_aborted.make_true
@@ -1,6 +1,8 @@
1
1
  class TLDR
2
2
  class Strategizer
3
- Strategy = Struct.new :prepend_thread_unsafe_tests, :parallel_tests_and_groups, :thread_unsafe_tests
3
+ Strategy = Struct.new :parallel?, :prepend_sequential_tests,
4
+ :parallel_tests_and_groups, :append_sequential_tests,
5
+ keyword_init: true
4
6
 
5
7
  # Combine all discovered test methods with any methods grouped by run_these_together!
6
8
  #
@@ -8,13 +10,15 @@ class TLDR
8
10
  # - Map over tests to build out groups in order to retain shuffle order
9
11
  # (group will run in position of first test in the group)
10
12
  # - If a test is in multiple groups, only run it once
11
- def strategize all_tests, run_these_together_groups, thread_unsafe_test_groups, prepend_paths
13
+ def strategize all_tests, run_these_together_groups, thread_unsafe_test_groups, config
14
+ return Strategy.new(parallel?: false) if run_sequentially?(all_tests, config)
15
+
12
16
  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)
17
+ prepend_sequential_tests, append_sequential_tests = partition_prepend(thread_unsafe_tests, config)
14
18
 
15
- grouped_tests = prepare_run_together_groups run_these_together_groups, thread_safe_tests, thread_unsafe_tests
19
+ grouped_tests = prepare_run_together_groups run_these_together_groups, thread_safe_tests, append_sequential_tests
16
20
  already_included_groups = []
17
- Strategy.new prepend_thread_unsafe_tests, thread_safe_tests.map { |test|
21
+ parallel_tests_and_groups = thread_safe_tests.map { |test|
18
22
  if (group = grouped_tests.find { |group| group.tests.include? test })
19
23
  if already_included_groups.include? group
20
24
  next
@@ -28,11 +32,21 @@ class TLDR
28
32
  else
29
33
  test
30
34
  end
31
- }.compact, thread_unsafe_tests
35
+ }.compact
36
+ Strategy.new(
37
+ parallel?: true,
38
+ prepend_sequential_tests:,
39
+ parallel_tests_and_groups:,
40
+ append_sequential_tests:
41
+ )
32
42
  end
33
43
 
34
44
  private
35
45
 
46
+ def run_sequentially? all_tests, config
47
+ all_tests.size < 2 || !config.parallel
48
+ end
49
+
36
50
  def partition_unsafe tests, thread_unsafe_test_groups
37
51
  tests.partition { |test|
38
52
  thread_unsafe_test_groups.any? { |group| group.tests.include? test }
@@ -41,8 +55,9 @@ class TLDR
41
55
 
42
56
  # Sadly duplicative with Planner.rb, necessitating the extraction of PathUtil
43
57
  # 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_search_locations PathUtil.expand_globs prepend_paths
58
+ def partition_prepend thread_unsafe_tests, config
59
+ prepend_paths = config.no_prepend ? [] : config.prepend_paths
60
+ locations = PathUtil.expand_paths prepend_paths
46
61
 
47
62
  thread_unsafe_tests.partition { |test|
48
63
  PathUtil.locations_include_test? locations, test
@@ -4,7 +4,7 @@ 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",
@@ -16,19 +16,22 @@ class TLDR
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_paths, :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,
25
+ CONFIG_ATTRIBUTES = [
26
+ :paths, :seed, :no_helper, :verbose, :reporter,
27
+ :helper_paths, :load_paths, :parallel, :names, :fail_fast, :no_emoji,
27
28
  :prepend_paths, :no_prepend, :exclude_paths, :exclude_names, :base_path,
28
- :no_dotfile,
29
+ :no_dotfile, :warnings,
29
30
  # Internal properties
30
- :config_intended_for_merge_only, :seed_set_intentionally, :cli_defaults,
31
- keyword_init: true do
31
+ :config_intended_for_merge_only, :seed_set_intentionally, :cli_defaults
32
+ ].freeze
33
+
34
+ Config = Struct.new(*CONFIG_ATTRIBUTES, keyword_init: true) do
32
35
  def initialize(**args)
33
36
  unless args[:config_intended_for_merge_only]
34
37
  change_working_directory_because_i_am_bad_and_i_should_feel_bad!(args[:base_path])
@@ -60,20 +63,21 @@ class TLDR
60
63
  no_prepend: false,
61
64
  exclude_paths: [],
62
65
  exclude_names: [],
63
- base_path: nil
66
+ base_path: nil,
67
+ warnings: true
64
68
  }
65
69
 
66
70
  if cli_defaults
67
71
  common.merge(
68
72
  paths: Dir["test/**/*_test.rb", "test/**/test_*.rb"],
69
- helper: "test/helper.rb",
73
+ helper_paths: ["test/helper.rb"],
70
74
  load_paths: ["test"],
71
75
  prepend_paths: [MOST_RECENTLY_MODIFIED_TAG]
72
76
  )
73
77
  else
74
78
  common.merge(
75
79
  paths: [],
76
- helper: nil,
80
+ helper_paths: [],
77
81
  load_paths: [],
78
82
  prepend_paths: []
79
83
  )
@@ -92,17 +96,17 @@ class TLDR
92
96
  defaults = Config.build_defaults(cli_defaults: merged_args[:cli_defaults])
93
97
 
94
98
  # Arrays
95
- [:paths, :load_paths, :names, :prepend_paths, :exclude_paths, :exclude_names].each do |key|
99
+ [:paths, :helper_paths, :load_paths, :names, :prepend_paths, :exclude_paths, :exclude_names].each do |key|
96
100
  merged_args[key] = defaults[key] if merged_args[key].nil? || merged_args[key].empty?
97
101
  end
98
102
 
99
103
  # Booleans
100
- [: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|
101
105
  merged_args[key] = defaults[key] if merged_args[key].nil?
102
106
  end
103
107
 
104
108
  # Values
105
- [:seed, :reporter, :helper].each do |key|
109
+ [:seed, :reporter].each do |key|
106
110
  merged_args[key] ||= defaults[key]
107
111
  end
108
112
 
@@ -166,7 +170,7 @@ class TLDR
166
170
  # Don't print prepended tests if they're disabled
167
171
  next
168
172
  end
169
- elsif key == :helper && no_helper
173
+ elsif key == :helper_paths && no_helper
170
174
  # Don't print the helper if it's disabled
171
175
  next
172
176
  elsif key == :parallel
@@ -176,6 +180,8 @@ class TLDR
176
180
  "--parallel"
177
181
  end
178
182
  next val
183
+ elsif key == :warnings && defaults[:warnings] != self[:warnings]
184
+ next warnings ? "--warnings" : "--no-warnings"
179
185
  end
180
186
 
181
187
  if defaults[key] == self[key]
@@ -231,6 +237,9 @@ class TLDR
231
237
  if dotfile_args.key?(:reporter)
232
238
  dotfile_args[:reporter] = Kernel.const_get(dotfile_args[:reporter])
233
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
234
243
 
235
244
  dotfile_args.merge(args)
236
245
  end
@@ -1,3 +1,3 @@
1
1
  class TLDR
2
- Plan = Struct.new :tests
2
+ Plan = Struct.new :tests, :strategy
3
3
  end
@@ -1,17 +1,17 @@
1
1
  class TLDR
2
- Test = Struct.new :klass, :method do
2
+ Test = Struct.new :test_class, :method_name do
3
3
  attr_reader :file, :line, :location
4
4
 
5
5
  def initialize(*args)
6
6
  super
7
- @file, @line = SorbetCompatibility.unwrap_method(klass.instance_method(method)).source_location
7
+ @file, @line = SorbetCompatibility.unwrap_method(test_class.instance_method(method_name)).source_location
8
8
  @location = Location.new file, line
9
9
  end
10
10
 
11
11
  # Memoizing at call time, because re-parsing isn't free and isn't usually necessary
12
12
  def end_line
13
13
  @end_line ||= begin
14
- test_method = SorbetCompatibility.unwrap_method klass.instance_method(method)
14
+ test_method = SorbetCompatibility.unwrap_method test_class.instance_method(method_name)
15
15
  RubyVM::AbstractSyntaxTree.of(test_method).last_lineno
16
16
  end
17
17
  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.5.0"
2
+ VERSION = "0.6.1"
3
3
  end
data/lib/tldr.rb CHANGED
@@ -3,9 +3,10 @@ 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"
8
+ require_relative "tldr/executor"
7
9
  require_relative "tldr/parallel_controls"
8
- require_relative "tldr/parallelizer"
9
10
  require_relative "tldr/path_util"
10
11
  require_relative "tldr/planner"
11
12
  require_relative "tldr/reporters"
@@ -40,6 +41,8 @@ class TLDR
40
41
  def self.at_exit! config = Config.new
41
42
  # Ignore at_exit when running tldr CLI, since that will run any tests
42
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" }
43
46
  # Ignore at_exit when we've already registered an at_exit hook
44
47
  return if @@at_exit_registered
45
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.5.0
4
+ version: 0.6.1
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-28 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,10 @@ 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
64
+ - lib/tldr/executor.rb
63
65
  - lib/tldr/parallel_controls.rb
64
- - lib/tldr/parallelizer.rb
65
66
  - lib/tldr/path_util.rb
66
67
  - lib/tldr/planner.rb
67
68
  - lib/tldr/rake.rb
@@ -106,7 +107,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
106
107
  - !ruby/object:Gem::Version
107
108
  version: '0'
108
109
  requirements: []
109
- rubygems_version: 3.4.6
110
+ rubygems_version: 3.4.17
110
111
  signing_key:
111
112
  specification_version: 4
112
113
  summary: TLDR will run your tests, but only for 1.8 seconds.