tldr 0.5.0 → 0.6.1

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: 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.