tldr 0.8.0 → 0.9.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: 81e3d6734a718c5abbf80cfb088369d5a6bb89b81fc261020d97e34cbd6903bb
4
- data.tar.gz: 1c4d0b5a53a948d868556b027f6fd6cdb4b9a96e67b459f0b2bf73e05a251d2f
3
+ metadata.gz: f6d49bfa66698f5e33451a20655ab442e6a6e50e76ec3b048b49d93baf7d8c66
4
+ data.tar.gz: f64c38ecd633686bc7616d4cf377fd49d2d91387dadaad09d81bf960ae2c39b6
5
5
  SHA512:
6
- metadata.gz: df9de67f0609adc2c5e0d2fc44a3bec44dd8a25a4ed0d128b8f1beaad6f45343dc9e5644e8d3b94b5bbe4db919f3becdf2e9a468a6eda0978669018135993d1f
7
- data.tar.gz: 00e813b4947ef3d9e267e3ea4248def3254829b4b910719ad4cdd258cf809d751d7c5a539d527a708f9ff3471fe45fe24fc342d132f15948a56d7d5a56b38c7e
6
+ metadata.gz: 524382d1fe9004b821bc7c7ea9b33779221ed60b35f51cf78924412c15a2c3cf07bb2ad46f8d5815f0890f3bd2ccfec868310521b00def3196ab93069e9153b0
7
+ data.tar.gz: 11313f640b6f2443063dbd79083f6810dca5778411c8093a36ebd5284b306c3db6be9dbc9e7bf53c4f63500734075371051243e75a665fb7e0e952ce28fdbe23
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## [0.9.1]
2
+
3
+ * Correctly clear the screen between runs
4
+
5
+ ## [0.9.0]
6
+
7
+ * Add a `--watch` option that will spawn fswatch | xargs and clear the screen
8
+ between runs (requires fswatch to gbe installed)
9
+ * Add "lib" as a default load path along with "test"
10
+
1
11
  ## [0.8.0]
2
12
 
3
13
  * Add a `--yes-i-know` flag that will suppress the large warning when your test
data/README.md CHANGED
@@ -116,15 +116,17 @@ Usage: tldr [options] some_tests/**/*.rb some/path.rb:13 ...
116
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
- -l, --load-path PATH Add one or more paths to the $LOAD_PATH (Default: ["test"])
119
+ -l, --load-path PATH Add one or more paths to the $LOAD_PATH (Default: ["lib", "test"])
120
120
  -r, --reporter REPORTER Set a custom reporter class (Default: "TLDR::Reporters::Default")
121
121
  --base-path PATH Change the working directory for all relative paths (Default: current working directory)
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
125
  --[no-]warnings Print Ruby warnings (Default: true)
126
+ --watch Run your tests continuously on file save (requires 'fswatch' to be installed)
126
127
  --yes-i-know Suppress TLDR report when suite runs over 1.8s
127
- --comment COMMENT No-op; used for multi-line execution instructions
128
+ --i-am-being-watched [INTERNAL] Signals to tldr it is being invoked under --watch mode
129
+ --comment COMMENT [INTERNAL] No-op; used for multi-line execution instructions
128
130
  ```
129
131
 
130
132
  After being parsed, all the CLI options are converted into a
@@ -161,6 +163,20 @@ with these caveats:
161
163
  TLDR::Assertions::MinitestCompatibility` into the `TLDR` base class or
162
164
  individual test classesJust set it
163
165
 
166
+ ### Running tests continuously with --watch
167
+
168
+ The `tldr` CLI includes a `--watch` option which will watch for changes in any
169
+ of the configured load paths (`["test", "lib"]` by default) and then execute
170
+ your tests each time a file is changed. To keep the output up-to-date and easy
171
+ to scan, it will also clear your console before each run.
172
+
173
+ Note that this feature requires you have
174
+ [fswatch](https://github.com/emcrisostomo/fswatch) installed and on your `PATH`
175
+
176
+ Here's what that might look like:
177
+
178
+ ![tldr-watch](https://github.com/tendersearls/tldr/assets/79303/364f0e52-5596-49ce-a470-5eaeddd11f03)
179
+
164
180
  ### Running TLDR with Rake
165
181
 
166
182
  TLDR ships with a [very](lib/tldr/rake.rb) minimal rake task that simply shells
@@ -240,8 +256,9 @@ encountered multiple times, only the first hook will be registered. If the
240
256
 
241
257
  #### Setting up the load path
242
258
 
243
- When running TLDR from a Ruby script, one thing the framework can't help you with
244
- is setting up load paths for you.
259
+ By default, the `tldr` CLI adds `test` and `lib` directories to the load path
260
+ for you, but when running TLDR from a Ruby script, it doesn't set those up for
261
+ you.
245
262
 
246
263
  If you want to require code in `test/` or `lib/` without using
247
264
  `require_relative`, you'll need to add those directories to the load path. You
@@ -350,7 +367,7 @@ TLDR is laser-focused on running tests, so it doesn't provide a built-in mocking
350
367
  facility. Might we interest you in a refreshing
351
368
  [mocktail](https://github.com/testdouble/mocktail), instead?
352
369
 
353
- ### Contributing to TLDR
370
+ ## Contributing to TLDR
354
371
 
355
372
  If you want to submit PRs on this repo, please know that the code style is
356
373
  [Kirkland-style Ruby](https://mastodon.social/@searls/111137666157318482), where
@@ -4,7 +4,7 @@ class TLDR
4
4
  class ArgvParser
5
5
  PATTERN_FRIENDLY_SPLITTER = /,(?=(?:[^\/]*\/[^\/]*\/)*[^\/]*$)/
6
6
 
7
- def parse(args, options = {cli_defaults: true})
7
+ def parse args, options = {cli_defaults: true}
8
8
  OptionParser.new do |opts|
9
9
  opts.banner = "Usage: tldr [options] some_tests/**/*.rb some/path.rb:13 ..."
10
10
 
@@ -22,12 +22,12 @@ class TLDR
22
22
 
23
23
  opts.on "-n", "#{CONFLAGS[:names]} PATTERN", "One or more names or /patterns/ of tests to run (like: foo_test, /test_foo.*/, Foo#foo_test)" do |name|
24
24
  options[:names] ||= []
25
- options[:names] += name.split PATTERN_FRIENDLY_SPLITTER
25
+ options[:names] += name.split(PATTERN_FRIENDLY_SPLITTER)
26
26
  end
27
27
 
28
28
  opts.on "#{CONFLAGS[:exclude_names]} PATTERN", "One or more names or /patterns/ NOT to run" do |exclude_name|
29
29
  options[:exclude_names] ||= []
30
- options[:exclude_names] += exclude_name.split PATTERN_FRIENDLY_SPLITTER
30
+ options[:exclude_names] += exclude_name.split(PATTERN_FRIENDLY_SPLITTER)
31
31
  end
32
32
 
33
33
  opts.on "#{CONFLAGS[:exclude_paths]} PATH", Array, "One or more paths NOT to run (like: foo.rb, \"test/bar/**\", baz.rb:3)" do |path|
@@ -53,7 +53,7 @@ class TLDR
53
53
  options[:no_prepend] = true
54
54
  end
55
55
 
56
- opts.on "-l", "#{CONFLAGS[:load_paths]} PATH", Array, "Add one or more paths to the $LOAD_PATH (Default: [\"test\"])" do |load_path|
56
+ opts.on "-l", "#{CONFLAGS[:load_paths]} PATH", Array, "Add one or more paths to the $LOAD_PATH (Default: [\"lib\", \"test\"])" do |load_path|
57
57
  options[:load_paths] ||= []
58
58
  options[:load_paths] += load_path
59
59
  end
@@ -82,11 +82,19 @@ class TLDR
82
82
  options[:warnings] = warnings
83
83
  end
84
84
 
85
+ opts.on CONFLAGS[:watch], "Run your tests continuously on file save (requires 'fswatch' to be installed)" do
86
+ options[:watch] = true
87
+ end
88
+
85
89
  opts.on CONFLAGS[:yes_i_know], "Suppress TLDR report when suite runs over 1.8s" do
86
90
  options[:yes_i_know] = true
87
91
  end
88
92
 
89
- opts.on "--comment COMMENT", String, "No-op; used for multi-line execution instructions" do
93
+ opts.on CONFLAGS[:i_am_being_watched], "[INTERNAL] Signals to tldr it is being invoked under --watch mode" do
94
+ options[:i_am_being_watched] = true
95
+ end
96
+
97
+ opts.on "--comment COMMENT", String, "[INTERNAL] No-op; used for multi-line execution instructions" do
90
98
  # See "--comment" in lib/tldr/reporters/default.rb for an example of how this is used internally
91
99
  end
92
100
  end.parse!(args)
@@ -34,7 +34,7 @@ class TLDR
34
34
  assert receiver.__send__(method, *args), message
35
35
  end
36
36
 
37
- def capture_io(&blk)
37
+ def capture_io &blk
38
38
  Assertions.capture_io(&blk)
39
39
  end
40
40
 
@@ -74,7 +74,7 @@ class TLDR
74
74
  end
75
75
 
76
76
  def assert_equal expected, actual, message = nil
77
- message = Assertions.msg(message) { Assertions.diff expected, actual }
77
+ message = Assertions.msg(message) { Assertions.diff(expected, actual) }
78
78
  assert expected == actual, message
79
79
  end
80
80
 
@@ -158,7 +158,7 @@ class TLDR
158
158
  "Expected #{Assertions.h(actual)} to match #{Assertions.h(matcher)}"
159
159
  }
160
160
  assert_respond_to matcher, :=~
161
- matcher = Regexp.new Regexp.escape matcher if String === matcher
161
+ matcher = Regexp.new(Regexp.escape(matcher)) if String === matcher
162
162
  assert matcher =~ actual, message
163
163
  Regexp.last_match
164
164
  end
@@ -294,7 +294,7 @@ class TLDR
294
294
  "---Backtrace---",
295
295
  TLDR.filter_backtrace(e.backtrace).join("\n"),
296
296
  "---------------"
297
- ].compact.join "\n"
297
+ ].compact.join("\n")
298
298
  }
299
299
  end
300
300
 
@@ -14,19 +14,19 @@ class TLDR
14
14
  private
15
15
 
16
16
  def trim_leading_frames backtrace
17
- if (trimmed = backtrace.take_while { |frame| meaningful? frame }).any?
17
+ if (trimmed = backtrace.take_while { |frame| meaningful?(frame) }).any?
18
18
  trimmed
19
19
  end
20
20
  end
21
21
 
22
22
  def trim_internal_frames backtrace
23
- if (trimmed = backtrace.select { |frame| meaningful? frame }).any?
23
+ if (trimmed = backtrace.select { |frame| meaningful?(frame) }).any?
24
24
  trimmed
25
25
  end
26
26
  end
27
27
 
28
28
  def meaningful? frame
29
- !internal? frame
29
+ !internal?(frame)
30
30
  end
31
31
 
32
32
  def internal? frame
@@ -35,6 +35,6 @@ class TLDR
35
35
  end
36
36
 
37
37
  def self.filter_backtrace backtrace
38
- BacktraceFilter.new.filter backtrace
38
+ BacktraceFilter.new.filter(backtrace)
39
39
  end
40
40
  end
@@ -2,13 +2,13 @@ class TLDR
2
2
  module ClassUtil
3
3
  def self.gather_descendants root_klass
4
4
  root_klass.subclasses + root_klass.subclasses.flat_map { |subklass|
5
- gather_descendants subklass
5
+ gather_descendants(subklass)
6
6
  }
7
7
  end
8
8
 
9
9
  def self.gather_tests klass
10
10
  klass.instance_methods.grep(/^test_/).sort.map { |method|
11
- Test.new klass, method
11
+ Test.new(klass, method)
12
12
  }
13
13
  end
14
14
  end
@@ -1,7 +1,7 @@
1
1
  class TLDR
2
2
  module PathUtil
3
3
  def self.expand_paths path_strings, globs: true
4
- path_strings = expand_globs path_strings if globs
4
+ path_strings = expand_globs(path_strings) if globs
5
5
 
6
6
  path_strings.flat_map { |path_string|
7
7
  File.directory?(path_string) ? Dir["#{path_string}/**/*.rb"] : path_string
@@ -10,7 +10,7 @@ class TLDR
10
10
  line_numbers = path_string.scan(/:(\d+)/).flatten.map(&:to_i)
11
11
 
12
12
  if line_numbers.any?
13
- line_numbers.map { |line_number| Location.new absolute_path, line_number }
13
+ line_numbers.map { |line_number| Location.new(absolute_path, line_number) }
14
14
  else
15
15
  [Location.new(absolute_path, nil)]
16
16
  end
data/lib/tldr/planner.rb CHANGED
@@ -8,14 +8,14 @@ class TLDR
8
8
 
9
9
  def plan config
10
10
  $VERBOSE = config.warnings
11
- search_locations = PathUtil.expand_paths config.paths, globs: false
11
+ search_locations = PathUtil.expand_paths(config.paths, globs: false)
12
12
 
13
- prepend_load_paths config
14
- require_test_helper config
15
- require_tests search_locations
13
+ prepend_load_paths(config)
14
+ require_test_helper(config)
15
+ require_tests(search_locations)
16
16
 
17
17
  tests = gather_tests
18
- config.update_after_gathering_tests! tests
18
+ config.update_after_gathering_tests!(tests)
19
19
  tests_to_run = prepend(
20
20
  shuffle(
21
21
  exclude_by_path(
@@ -40,7 +40,7 @@ class TLDR
40
40
  config
41
41
  )
42
42
 
43
- Plan.new tests_to_run, strategy
43
+ Plan.new(tests_to_run, strategy)
44
44
  end
45
45
 
46
46
  private
@@ -53,9 +53,9 @@ class TLDR
53
53
 
54
54
  def prepend tests, config
55
55
  return tests if config.no_prepend
56
- prepended_locations = PathUtil.expand_paths config.prepend_paths
56
+ prepended_locations = PathUtil.expand_paths(config.prepend_paths)
57
57
  prepended, rest = tests.partition { |test|
58
- PathUtil.locations_include_test? prepended_locations, test
58
+ PathUtil.locations_include_test?(prepended_locations, test)
59
59
  }
60
60
  prepended + rest
61
61
  end
@@ -65,18 +65,18 @@ class TLDR
65
65
  end
66
66
 
67
67
  def exclude_by_path tests, exclude_paths
68
- excluded_locations = PathUtil.expand_paths exclude_paths
68
+ excluded_locations = PathUtil.expand_paths(exclude_paths)
69
69
  return tests if excluded_locations.empty?
70
70
 
71
71
  tests.reject { |test|
72
- PathUtil.locations_include_test? excluded_locations, test
72
+ PathUtil.locations_include_test?(excluded_locations, test)
73
73
  }
74
74
  end
75
75
 
76
76
  def exclude_by_name tests, exclude_names
77
77
  return tests if exclude_names.empty?
78
78
 
79
- name_excludes = expand_names_with_patterns exclude_names
79
+ name_excludes = expand_names_with_patterns(exclude_names)
80
80
 
81
81
  tests.reject { |test|
82
82
  name_excludes.any? { |filter|
@@ -90,14 +90,14 @@ class TLDR
90
90
  return tests if line_specific_locations.empty?
91
91
 
92
92
  tests.select { |test|
93
- PathUtil.locations_include_test? line_specific_locations, test
93
+ PathUtil.locations_include_test?(line_specific_locations, test)
94
94
  }
95
95
  end
96
96
 
97
97
  def filter_by_name tests, names
98
98
  return tests if names.empty?
99
99
 
100
- name_filters = expand_names_with_patterns names
100
+ name_filters = expand_names_with_patterns(names)
101
101
 
102
102
  tests.select { |test|
103
103
  name_filters.any? { |filter|
@@ -108,7 +108,7 @@ class TLDR
108
108
 
109
109
  def prepend_load_paths config
110
110
  config.load_paths.each do |load_path|
111
- $LOAD_PATH.unshift File.expand_path(load_path, Dir.pwd)
111
+ $LOAD_PATH.unshift(File.expand_path(load_path, Dir.pwd))
112
112
  end
113
113
  end
114
114
 
@@ -130,7 +130,7 @@ class TLDR
130
130
  def expand_names_with_patterns names
131
131
  names.map { |name|
132
132
  if name.is_a?(String) && name =~ /^\/(.*)\/$/
133
- Regexp.new $1
133
+ Regexp.new($1)
134
134
  else
135
135
  name
136
136
  end
data/lib/tldr/rake.rb CHANGED
@@ -7,7 +7,7 @@ class TLDR
7
7
  class Task
8
8
  include Rake::DSL
9
9
 
10
- def initialize(name: "tldr", config: Config.new)
10
+ def initialize name: "tldr", config: Config.new
11
11
  define name, config
12
12
  end
13
13
 
@@ -1,7 +1,7 @@
1
1
  class TLDR
2
2
  module Reporters
3
3
  class Base
4
- def initialize(config, out = $stdout, err = $stderr)
4
+ def initialize config, out = $stdout, err = $stderr
5
5
  out.sync = true
6
6
  err.sync = true
7
7
 
@@ -1,13 +1,14 @@
1
1
  class TLDR
2
2
  module Reporters
3
3
  class Default < Base
4
- def initialize(config, out = $stdout, err = $stderr)
4
+ def initialize config, out = $stdout, err = $stderr
5
5
  super
6
6
  @icons = @config.no_emoji ? IconProvider::Base.new : IconProvider::Emoji.new
7
7
  end
8
8
 
9
9
  def before_suite tests
10
- @suite_start_time = Process.clock_gettime Process::CLOCK_MONOTONIC, :microsecond
10
+ clear_screen_if_being_watched!
11
+ @suite_start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
11
12
  @out.print <<~MSG
12
13
  Command: #{tldr_command} #{@config.to_full_args}
13
14
  #{@icons.seed} #{CONFLAGS[:seed]} #{@config.seed}
@@ -32,7 +33,7 @@ class TLDR
32
33
  end
33
34
 
34
35
  def after_tldr planned_tests, wip_tests, test_results
35
- stop_time = Process.clock_gettime Process::CLOCK_MONOTONIC, :microsecond
36
+ stop_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
36
37
 
37
38
  @out.print @icons.tldr
38
39
  @err.print "\n\n"
@@ -45,7 +46,7 @@ class TLDR
45
46
  "too long; didn't run!",
46
47
  "#{@icons.run} Completed #{test_results.size} of #{planned_tests.size} tests (#{((test_results.size.to_f / planned_tests.size) * 100).round}%) before running out of time.",
47
48
  (<<~WIP.chomp if wip_tests.any?),
48
- #{@icons.wip} #{plural wip_tests.size, "test was", "tests were"} cancelled in progress:
49
+ #{@icons.wip} #{plural(wip_tests.size, "test was", "tests were")} cancelled in progress:
49
50
  #{wip_tests.map { |wip_test| " #{time_diff(wip_test.start_time, stop_time)}ms - #{describe(wip_test.test)}" }.join("\n")}
50
51
  WIP
51
52
  (<<~SLOW.chomp if test_results.any?),
@@ -58,7 +59,7 @@ class TLDR
58
59
  end
59
60
  end
60
61
 
61
- after_suite test_results
62
+ after_suite(test_results)
62
63
  end
63
64
 
64
65
  def after_fail_fast planned_tests, wip_tests, test_results, last_result
@@ -68,17 +69,17 @@ class TLDR
68
69
  wrap_in_horizontal_rule do
69
70
  @err.print [
70
71
  "Failing fast after #{describe(last_result.test, last_result.relevant_location)} #{last_result.error? ? "errored" : "failed"}.",
71
- ("#{@icons.wip} #{plural wip_tests.size, "test was", "tests were"} cancelled in progress." if wip_tests.any?),
72
- ("#{@icons.not_run} #{plural unrun_tests.size, "test was", "tests were"} not run at all." if unrun_tests.any?),
72
+ ("#{@icons.wip} #{plural(wip_tests.size, "test was", "tests were")} cancelled in progress." if wip_tests.any?),
73
+ ("#{@icons.not_run} #{plural(unrun_tests.size, "test was", "tests were")} not run at all." if unrun_tests.any?),
73
74
  describe_tests_that_didnt_finish(planned_tests, test_results)
74
75
  ].compact.join("\n\n")
75
76
  end
76
77
 
77
- after_suite test_results
78
+ after_suite(test_results)
78
79
  end
79
80
 
80
81
  def after_suite test_results
81
- duration = time_diff @suite_start_time
82
+ duration = time_diff(@suite_start_time)
82
83
  test_results = test_results.sort_by { |result| [result.test.location.file, result.test.location.line] }
83
84
 
84
85
  @err.print summarize_failures(test_results).join("\n\n")
@@ -112,7 +113,7 @@ class TLDR
112
113
  failures = results.select { |result| result.failing? }
113
114
  return failures if failures.empty?
114
115
 
115
- ["\n\nFailing tests:"] + failures.map.with_index { |result, i| summarize_result result, i }
116
+ ["\n\nFailing tests:"] + failures.map.with_index { |result, i| summarize_result(result, i) }
116
117
  end
117
118
 
118
119
  def summarize_result result, index
@@ -151,15 +152,15 @@ class TLDR
151
152
  unrun = planned_tests - test_results.map(&:test)
152
153
  return if unrun.empty?
153
154
 
154
- unrun_locators = consolidate unrun
155
+ unrun_locators = consolidate(unrun)
155
156
  failed = test_results.select(&:failing?).map(&:test)
156
- failed_locators = consolidate failed, exclude: unrun_locators
157
+ failed_locators = consolidate(failed, exclude: unrun_locators)
157
158
  suggested_locators = unrun_locators + [
158
- ("--comment \"Also include #{plural failed.size, "test"} that failed:\"" if failed_locators.any?)
159
+ ("--comment \"Also include #{plural(failed.size, "test")} that failed:\"" if failed_locators.any?)
159
160
  ].compact + failed_locators
160
161
  <<~MSG
161
- #{@icons.rock_on} Run the #{plural unrun.size, "test"} that didn't finish:
162
- #{tldr_command} #{@config.to_full_args exclude: [:paths]} #{suggested_locators.join(" \\\n ")}
162
+ #{@icons.rock_on} Run the #{plural(unrun.size, "test")} that didn't finish:
163
+ #{tldr_command} #{@config.to_full_args(exclude: [:paths])} #{suggested_locators.join(" \\\n ")}
163
164
  MSG
164
165
  end
165
166
 
@@ -172,6 +173,12 @@ class TLDR
172
173
  def tldr_command
173
174
  "#{"bundle exec " if defined?(Bundler)}tldr"
174
175
  end
176
+
177
+ def clear_screen_if_being_watched!
178
+ if @config.i_am_being_watched
179
+ @out.print "\e[2J\e[f"
180
+ end
181
+ end
175
182
  end
176
183
  end
177
184
  end
data/lib/tldr/runner.rb CHANGED
@@ -6,7 +6,7 @@ class TLDR
6
6
  @executor = Executor.new
7
7
  @wip = Concurrent::Array.new
8
8
  @results = Concurrent::Array.new
9
- @run_aborted = Concurrent::AtomicBoolean.new false
9
+ @run_aborted = Concurrent::AtomicBoolean.new(false)
10
10
  end
11
11
 
12
12
  def run config, plan
@@ -20,11 +20,11 @@ class TLDR
20
20
  next if ENV["CI"] && !$stderr.tty?
21
21
  next if @run_aborted.true?
22
22
  @run_aborted.make_true
23
- reporter.after_tldr plan.tests, @wip.dup, @results.dup
24
- exit! 3
23
+ reporter.after_tldr(plan.tests, @wip.dup, @results.dup)
24
+ exit!(3)
25
25
  end
26
26
 
27
- sleep 1.8
27
+ sleep(1.8)
28
28
  # Don't hard-kill the runner if user is debugging, it'll
29
29
  # screw up their terminal slash be a bad time
30
30
  if IRB.CurrentContext
@@ -41,8 +41,8 @@ class TLDR
41
41
  end
42
42
 
43
43
  unless @run_aborted.true?
44
- reporter.after_suite results
45
- exit exit_code results
44
+ reporter.after_suite(results)
45
+ exit(exit_code(results))
46
46
  end
47
47
  end
48
48
 
@@ -51,12 +51,12 @@ class TLDR
51
51
  def run_test test, config, plan, reporter
52
52
  e = nil
53
53
  start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
54
- wip_test = WIPTest.new test, start_time
54
+ wip_test = WIPTest.new(test, start_time)
55
55
  @wip << wip_test
56
56
  runtime = time_it(start_time) do
57
57
  instance = test.test_class.new
58
- instance.setup if instance.respond_to? :setup
59
- if instance.respond_to? :around
58
+ instance.setup if instance.respond_to?(:setup)
59
+ if instance.respond_to?(:around)
60
60
  did_run = false
61
61
  instance.around {
62
62
  did_run = true
@@ -66,15 +66,15 @@ class TLDR
66
66
  else
67
67
  instance.send(test.method_name)
68
68
  end
69
- instance.teardown if instance.respond_to? :teardown
69
+ instance.teardown if instance.respond_to?(:teardown)
70
70
  rescue Skip, Failure, StandardError => e
71
71
  end
72
72
  TestResult.new(test, e, runtime).tap do |result|
73
73
  next if @run_aborted.true?
74
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
75
+ @wip.delete(wip_test)
76
+ reporter.after_test(result)
77
+ fail_fast(reporter, plan, result) if result.failing? && config.fail_fast
78
78
  end
79
79
  end
80
80
 
@@ -82,8 +82,8 @@ class TLDR
82
82
  unless @run_aborted.true?
83
83
  @run_aborted.make_true
84
84
  abort = proc do
85
- reporter.after_fail_fast plan.tests, @wip.dup, @results.dup, fast_failed_result
86
- exit! exit_code([fast_failed_result])
85
+ reporter.after_fail_fast(plan.tests, @wip.dup, @results.dup, fast_failed_result)
86
+ exit!(exit_code([fast_failed_result]))
87
87
  end
88
88
 
89
89
  if IRB.CurrentContext
@@ -94,7 +94,7 @@ class TLDR
94
94
  end
95
95
  end
96
96
 
97
- def time_it(start)
97
+ def time_it start
98
98
  yield
99
99
  ((Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond) - start) / 1000.0).round
100
100
  end
@@ -1,8 +1,8 @@
1
1
  class TLDR
2
2
  class Strategizer
3
- Strategy = Struct.new :parallel?, :prepend_sequential_tests,
3
+ Strategy = Struct.new(:parallel?, :prepend_sequential_tests,
4
4
  :parallel_tests_and_groups, :append_sequential_tests,
5
- keyword_init: true
5
+ keyword_init: true)
6
6
 
7
7
  # Combine all discovered test methods with any methods grouped by run_these_together!
8
8
  #
@@ -16,11 +16,11 @@ class TLDR
16
16
  thread_unsafe_tests, thread_safe_tests = partition_unsafe(all_tests, thread_unsafe_test_groups)
17
17
  prepend_sequential_tests, append_sequential_tests = partition_prepend(thread_unsafe_tests, config)
18
18
 
19
- grouped_tests = prepare_run_together_groups run_these_together_groups, thread_safe_tests, append_sequential_tests
19
+ grouped_tests = prepare_run_together_groups(run_these_together_groups, thread_safe_tests, append_sequential_tests)
20
20
  already_included_groups = []
21
21
  parallel_tests_and_groups = thread_safe_tests.map { |test|
22
- if (group = grouped_tests.find { |group| group.tests.include? test })
23
- if already_included_groups.include? group
22
+ if (group = grouped_tests.find { |group| group.tests.include?(test) })
23
+ if already_included_groups.include?(group)
24
24
  next
25
25
  elsif (other = already_included_groups.find { |other| (group.tests & other.tests).any? })
26
26
  other.tests |= group.tests
@@ -49,7 +49,7 @@ class TLDR
49
49
 
50
50
  def partition_unsafe tests, thread_unsafe_test_groups
51
51
  tests.partition { |test|
52
- thread_unsafe_test_groups.any? { |group| group.tests.include? test }
52
+ thread_unsafe_test_groups.any? { |group| group.tests.include?(test) }
53
53
  }
54
54
  end
55
55
 
@@ -57,10 +57,10 @@ class TLDR
57
57
  # Suboptimal, but we do indeed need to do this work in two places ¯\_(ツ)_/¯
58
58
  def partition_prepend thread_unsafe_tests, config
59
59
  prepend_paths = config.no_prepend ? [] : config.prepend_paths
60
- locations = PathUtil.expand_paths prepend_paths
60
+ locations = PathUtil.expand_paths(prepend_paths)
61
61
 
62
62
  thread_unsafe_tests.partition { |test|
63
- PathUtil.locations_include_test? locations, test
63
+ PathUtil.locations_include_test?(locations, test)
64
64
  }
65
65
  end
66
66
 
@@ -17,7 +17,9 @@ class TLDR
17
17
  base_path: "--base-path",
18
18
  no_dotfile: "--no-dotfile",
19
19
  warnings: "--[no-]warnings",
20
+ watch: "--watch",
20
21
  yes_i_know: "--yes-i-know",
22
+ i_am_being_watched: "--i-am-being-watched",
21
23
  paths: nil
22
24
  }.freeze
23
25
 
@@ -27,7 +29,7 @@ class TLDR
27
29
  :paths, :seed, :no_helper, :verbose, :reporter,
28
30
  :helper_paths, :load_paths, :parallel, :names, :fail_fast, :no_emoji,
29
31
  :prepend_paths, :no_prepend, :exclude_paths, :exclude_names, :base_path,
30
- :no_dotfile, :warnings, :yes_i_know,
32
+ :no_dotfile, :warnings, :watch, :yes_i_know, :i_am_being_watched,
31
33
  # Internal properties
32
34
  :config_intended_for_merge_only, :seed_set_intentionally, :cli_defaults
33
35
  ].freeze
@@ -66,14 +68,16 @@ class TLDR
66
68
  exclude_names: [],
67
69
  base_path: nil,
68
70
  warnings: true,
69
- yes_i_know: false
71
+ watch: false,
72
+ yes_i_know: false,
73
+ i_am_being_watched: false
70
74
  }
71
75
 
72
76
  if cli_defaults
73
77
  common.merge(
74
78
  paths: Dir["test/**/*_test.rb", "test/**/test_*.rb"],
75
79
  helper_paths: ["test/helper.rb"],
76
- load_paths: ["test"],
80
+ load_paths: ["lib", "test"],
77
81
  prepend_paths: [MOST_RECENTLY_MODIFIED_TAG]
78
82
  )
79
83
  else
@@ -103,7 +107,7 @@ class TLDR
103
107
  end
104
108
 
105
109
  # Booleans
106
- [:no_helper, :verbose, :fail_fast, :no_emoji, :no_prepend, :warnings, :yes_i_know].each do |key|
110
+ [:no_helper, :verbose, :fail_fast, :no_emoji, :no_prepend, :warnings, :yes_i_know, :i_am_being_watched].each do |key|
107
111
  merged_args[key] = defaults[key] if merged_args[key].nil?
108
112
  end
109
113
 
@@ -131,26 +135,34 @@ class TLDR
131
135
 
132
136
  self.prepend_paths = prepend_paths.map { |path|
133
137
  if path == MOST_RECENTLY_MODIFIED_TAG
134
- most_recently_modified_test_file tests
138
+ most_recently_modified_test_file(tests)
135
139
  else
136
140
  path
137
141
  end
138
142
  }.compact
139
143
  end
140
144
 
141
- def to_full_args(exclude: [])
142
- to_cli_argv(
145
+ def to_full_args exclude: [], ensure_args: []
146
+ argv = to_cli_argv(
143
147
  CONFLAGS.keys -
144
148
  exclude - [
145
- (:seed unless seed_set_intentionally)
149
+ (:seed unless seed_set_intentionally),
150
+ :watch,
151
+ :i_am_being_watched
146
152
  ]
147
- ).join(" ")
153
+ )
154
+
155
+ ensure_args.each do |arg|
156
+ argv << arg unless argv.include?(arg)
157
+ end
158
+
159
+ argv.join(" ")
148
160
  end
149
161
 
150
- def to_single_path_args(path)
162
+ def to_single_path_args path
151
163
  argv = to_cli_argv(CONFLAGS.keys - [
152
164
  :seed, :parallel, :names, :fail_fast, :paths, :prepend_paths,
153
- :no_prepend, :exclude_paths
165
+ :no_prepend, :exclude_paths, :watch, :i_am_being_watched
154
166
  ])
155
167
 
156
168
  (argv + [stringify(:paths, path)]).join(" ")
@@ -212,7 +224,7 @@ class TLDR
212
224
  end
213
225
  end
214
226
 
215
- def most_recently_modified_test_file(tests)
227
+ def most_recently_modified_test_file tests
216
228
  return if tests.empty?
217
229
 
218
230
  tests.max_by { |test| File.mtime(test.file) }.file
@@ -222,11 +234,11 @@ class TLDR
222
234
  # ASAP, even before globbing to find default paths of tests. If there is
223
235
  # a way to change all of our Dir.glob calls to be relative to base_path
224
236
  # without a loss in accuracy, would love to not have to use Dir.chdir!
225
- def change_working_directory_because_i_am_bad_and_i_should_feel_bad!(base_path)
237
+ def change_working_directory_because_i_am_bad_and_i_should_feel_bad! base_path
226
238
  Dir.chdir(base_path) unless base_path.nil?
227
239
  end
228
240
 
229
- def merge_dotfile_args(args)
241
+ def merge_dotfile_args args
230
242
  return args if args[:no_dotfile] || !File.exist?(".tldr.yml")
231
243
  require "yaml"
232
244
 
@@ -1,5 +1,5 @@
1
1
  class TLDR
2
- Location = Struct.new :file, :line do
2
+ Location = Struct.new(:file, :line) do
3
3
  def relative
4
4
  if file.start_with?(Dir.pwd)
5
5
  file[Dir.pwd.length + 1..]
@@ -1,3 +1,3 @@
1
1
  class TLDR
2
- Plan = Struct.new :tests, :strategy
2
+ Plan = Struct.new(:tests, :strategy)
3
3
  end
@@ -1,17 +1,17 @@
1
1
  class TLDR
2
- Test = Struct.new :test_class, :method_name 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
7
  @file, @line = SorbetCompatibility.unwrap_method(test_class.instance_method(method_name)).source_location
8
- @location = Location.new file, line
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 test_class.instance_method(method_name)
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
@@ -1,16 +1,16 @@
1
1
  class TLDR
2
- TestGroup = Struct.new :configuration do
2
+ TestGroup = Struct.new(:configuration) do
3
3
  attr_writer :tests
4
4
 
5
5
  def tests
6
6
  @tests ||= configuration.flat_map { |(klass, method)|
7
- klass = Kernel.const_get(klass) if klass.is_a? String
7
+ klass = Kernel.const_get(klass) if klass.is_a?(String)
8
8
  if method.nil?
9
9
  ([klass] + ClassUtil.gather_descendants(klass)).flat_map { |klass|
10
10
  ClassUtil.gather_tests(klass)
11
11
  }
12
12
  else
13
- Test.new klass, method
13
+ Test.new(klass, method)
14
14
  end
15
15
  }
16
16
  end
@@ -1,5 +1,5 @@
1
1
  class TLDR
2
- TestResult = Struct.new :test, :error, :runtime do
2
+ TestResult = Struct.new(:test, :error, :runtime) do
3
3
  attr_reader :type, :error_location
4
4
 
5
5
  def initialize(*args)
@@ -1,3 +1,3 @@
1
1
  class TLDR
2
- WIPTest = Struct.new :test, :start_time
2
+ WIPTest = Struct.new(:test, :start_time)
3
3
  end
data/lib/tldr/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class TLDR
2
- VERSION = "0.8.0"
2
+ VERSION = "0.9.1"
3
3
  end
@@ -0,0 +1,35 @@
1
+ class TLDR
2
+ class Watcher
3
+ def watch config
4
+ require_fs_watch!
5
+ tldr_command = "#{"bundle exec " if defined?(Bundler)}tldr #{config.to_full_args(ensure_args: ["--i-am-being-watched"])}"
6
+ command = "fswatch -o #{config.load_paths.reverse.join(" ")} | xargs -n1 -I{} #{tldr_command}"
7
+
8
+ puts <<~MSG
9
+
10
+ Watching for changes in #{config.load_paths.map(&:inspect).join(", ")}...
11
+
12
+ When a file changes, TLDR will run this command:
13
+
14
+ $ #{tldr_command}
15
+
16
+ MSG
17
+
18
+ exec command
19
+ end
20
+
21
+ private
22
+
23
+ def require_fs_watch!
24
+ `which fswatch`
25
+ return if $?.success?
26
+
27
+ warn <<~MSG
28
+ Error: fswatch must be installed and on your PATH to run TLDR in --watch mode
29
+
30
+ See: https://github.com/emcrisostomo/fswatch
31
+ MSG
32
+ exit 1
33
+ end
34
+ end
35
+ end
data/lib/tldr.rb CHANGED
@@ -16,6 +16,7 @@ require_relative "tldr/sorbet_compatibility"
16
16
  require_relative "tldr/strategizer"
17
17
  require_relative "tldr/value"
18
18
  require_relative "tldr/version"
19
+ require_relative "tldr/watcher"
19
20
 
20
21
  class TLDR
21
22
  include Assertions
@@ -29,25 +30,29 @@ class TLDR
29
30
 
30
31
  module Run
31
32
  def self.cli argv
32
- config = ArgvParser.new.parse argv
33
- tests config
33
+ config = ArgvParser.new.parse(argv)
34
+ tests(config)
34
35
  end
35
36
 
36
37
  def self.tests config = Config.new
37
- Runner.new.run config, Planner.new.plan(config)
38
+ if config.watch
39
+ Watcher.new.watch(config)
40
+ else
41
+ Runner.new.run(config, Planner.new.plan(config))
42
+ end
38
43
  end
39
44
 
40
45
  @@at_exit_registered = false
41
46
  def self.at_exit! config = Config.new
42
47
  # Ignore at_exit when running tldr CLI, since that will run any tests
43
- return if $PROGRAM_NAME.end_with? "tldr"
48
+ return if $PROGRAM_NAME.end_with?("tldr")
44
49
  # Also ignore if we're running from within our rake task
45
- return if caller.any? { |line| line.include? "lib/tldr/rake.rb" }
50
+ return if caller.any? { |line| line.include?("lib/tldr/rake.rb") }
46
51
  # Ignore at_exit when we've already registered an at_exit hook
47
52
  return if @@at_exit_registered
48
53
 
49
54
  Kernel.at_exit do
50
- Run.tests config
55
+ Run.tests(config)
51
56
  end
52
57
 
53
58
  @@at_exit_registered = true
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.8.0
4
+ version: 0.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Searls
@@ -85,6 +85,7 @@ files:
85
85
  - lib/tldr/value/test_result.rb
86
86
  - lib/tldr/value/wip_test.rb
87
87
  - lib/tldr/version.rb
88
+ - lib/tldr/watcher.rb
88
89
  - script/setup
89
90
  - script/test
90
91
  homepage: https://github.com/tenderlove/tldr