tldr 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e62dd10f12e7e2f2e2a691e0679207f4f3231b916b54b6fc51acbe51cc899918
4
- data.tar.gz: 2d29248e49b135cc840e3ad249f067ee0a4b706a730889c806c3385f0791f54c
3
+ metadata.gz: d60d27a492477aa1034acf117993630307f595690f97d356da763fb352f4837e
4
+ data.tar.gz: 240ee628207bf5b79568fff7ef93b5e6174825b8a53748e2b453076d2d00db7a
5
5
  SHA512:
6
- metadata.gz: 3a02db5e6c8c42a2d507a5b6ff99867060fd902bf94ef980546d052c535577db4174a6e3ab05e7146876ca4fc7f992030f40daf425a96c3d82e1439fd27b980c
7
- data.tar.gz: d580e9a4176c182da622ff826e2d7df64e9ed52f71efe933a22f6dff32d4e8ed60953e5062fccd23f8a2e24db4f0ed26ecaa00144dd5261728fa8b804e8f5c5b
6
+ metadata.gz: 9e513372aedda6a808ee61e426fc3c738e1f7837fe3ccb55ec658153c15998b4eab9da976680f062d936c638e6fcdcbdcdfabec181137acc48ce6187b5d4aa69
7
+ data.tar.gz: 4fa415b7f6858e8a26766232a251542f28e7485ead112dd514d72fb55364ae54520ff1d3585dada05e8e56861295dfe458922f4038b1cdb67ee7ec1c4883f382
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  ## unreleased
2
2
 
3
+ ## [1.1.0]
4
+
5
+ * Add `--exit-0-on-timeout` to allow the suite timeout to be used more as a budget constraint (e.g. "I know my full suite is going to take 30 seconds to run but I want to constantly run as many as I can in 500ms to get fast feedback")
6
+ * Add `--exit-2-on-failure` flag to exit with status 2 for test failures (not just errors), useful for Claude Code hooks which only block on exit code 2
7
+
3
8
  ## [1.0.0]
4
9
 
5
10
  * **BREAKING** you know how the whole point of TLDR is that it aborts your test
data/README.md CHANGED
@@ -1,16 +1,16 @@
1
- # TLDR - Ruby tests for people who prioritize fast feedback
1
+ # TLDR - Testing for rubyists who want fast feedback
2
2
 
3
3
  TLDR is a suspiciously-delightful testing framework for Ruby.
4
4
 
5
5
  As a test library, TLDR is largely [API-compatible with Minitest](#minitest-compatibility). As a test runner, TLDR boasts a few features RSpec's CLI still doesn't have.
6
6
 
7
- The library, command line interface, and every decision in-between was prioritized to maximize productivity by promote fast feedback loops. Some highlights:
7
+ The library, command line interface, and every decision in-between was prioritized to maximize productivity to promote fast feedback loops. Some highlights:
8
8
 
9
- * Numerous ways to run specific tests: by path (`foo_test.rb`), line number (`foo_test.rb:13`), name (`--name test_foo`), or regex pattern (`-n /_foo_/`)
9
+ * Numerous ways to run specific tests: path (`foo_test.rb`), line number (`foo_test.rb:13`), name (`--name test_foo`), or regex pattern (`-n /_foo/`)
10
10
  * Parallel test execution by default, as well as [controls for serial execution of thread-unsafe tests](#parallel-by-default-is-nice-in-theory-but-half-my-tests-are-failing-wat)
11
11
  * Continuously run [after every file change](#running-tests-continuously-with---watch) with `--watch`
12
12
  * An optional timer to [enforce your tests never get slow](#enforcing-a-testing---timeout) with `--timeout`
13
- * A `--fail-fast` flag that aborts the run as soon as a failure is encountered
13
+ * A `--fail-fast` flag that aborts the run [as soon as a failure is encountered](#failing-fast-and-first)
14
14
  * Running your most-recently-edited test before all the others (see `--prepend`)
15
15
  * Delightful diffs when assertions fail, care of [super_diff](https://github.com/splitwise/super_diff)
16
16
 
@@ -18,9 +18,9 @@ We hope you'll give it a try!
18
18
 
19
19
  ## Getting started
20
20
 
21
- You can either read the rest of this README and learn about TLDR passively, or you can just clone this [TLDR demo repo](https://github/searls/tldr_demo) and work through its README as you play with its tests and run them with various options.
21
+ You can either read the rest of this README and learn about TLDR passively, or you can just clone this [TLDR demo repo](https://github.com/searls/tldr_demo) and work through its README as you play with its tests and run them with various options.
22
22
 
23
- **JUST IN CASE YOU'RE ALREADY SKIMMING THIS**, I said stop reading and [clone this interactive repo](https://github/searls/tldr_demo) if you're a hands-on learner.
23
+ **JUST IN CASE YOU'RE ALREADY SKIMMING THIS**, I said stop reading and [clone this interactive repo](https://github.com/searls/tldr_demo) if you're a hands-on learner.
24
24
 
25
25
  ### Install
26
26
 
@@ -61,15 +61,17 @@ Usage: tldr [options] some_tests/**/*.rb some/path.rb:13 ...
61
61
  --[no-]warnings Print Ruby warnings (Default: true)
62
62
  -v, --verbose Print stack traces for errors
63
63
  --yes-i-know Suppress TLDR report when suite runs beyond any configured --timeout
64
+ --exit-0-on-timeout Exit with status code 0 when suite times out instead of 3
65
+ --exit-2-on-failure Exit with status code 2 (normally for errors) for both failures and errors
64
66
  --print-interrupted-test-backtraces
65
67
  Print stack traces of tests interrupted after a timeout
66
68
  ```
67
69
 
68
70
  ### Setting defaults in .tldr.yml
69
71
 
70
- The `tldr` CLI will look for a `.tldr.yml` that can set the same set of options supported by the CLI file in the root of your project. You can specify a custom YAML location with `--config some/path.yml` if you need to.
72
+ The `tldr` CLI will look for a `.tldr.yml` file in the root of your project that can set all the same options supported by the CLI. You can specify a custom YAML location with `--config some/path.yml` if you want it to live someplace else.
71
73
 
72
- Any options found in the dotfile will override TLDR's defaults, but can still be overridden by the `tldr` CLI or a `TLDR::Config` object passed to [TLDR::Run.at_exit!](#running-tests-without-the-cli).
74
+ Any options found in the dotfile will override TLDR's defaults, but can still be overridden by the `tldr` CLI or a `TLDR::Config` object when running tests programmatically.
73
75
 
74
76
  Here's an [example project](/example/c) that specifies a `.tldr.yml` file as well as some [internal tests](/tests/dotfile_test.rb) demonstrating its behavior.
75
77
 
@@ -77,7 +79,7 @@ Here's an [example project](/example/c) that specifies a `.tldr.yml` file as wel
77
79
 
78
80
  If you've ever seen a [Minitest](https://github.com/minitest/minitest?tab=readme-ov-file#synopsis-) test, then you already know how to write TLDR tests. Rather than document how to write tests, this section just highlights the ways TLDR tests differ from Minitest tests.
79
81
 
80
- First, instead of inheriting from `Minitest::Test`, your test classes must subclass `TLDR` instead:
82
+ First, instead of inheriting from `Minitest::Test`, TLDR test classes should descend from (wait for it) the `TLDR` base class:
81
83
 
82
84
  ```ruby
83
85
  class MyTest < TLDR
@@ -87,7 +89,7 @@ class MyTest < TLDR
87
89
  end
88
90
  ```
89
91
 
90
- Second, if your tests depend on a test helper, so long as you name it `test/helper.rb`, it will automatically be required by TLDR, so you don't need to add `require "helper"` at the top of each test. If you want to name the helper something else, you can do so with the `--helper` option:
92
+ Second, if your tests depend on a test helper, it will be automatically loaded by TLDR _if_ you name it `test/helper.rb`. That means you don't need to add `require "helper"` to the top of every test. If you want to name the helper something else, you can do so with the `--helper` option:
91
93
 
92
94
  ```
93
95
  tldr --helper test/test_helper.rb
@@ -98,13 +100,13 @@ Third, TLDR offers fewer features:
98
100
  * No built-in mock library ([use mocktail](https://justin.searls.co/posts/a-real-world-mocktail-example-test/), maybe!)
99
101
  * No "spec" API
100
102
  * No benchmark tool
101
- * No test bisect script
103
+ * No bisect script
102
104
 
103
- And that's it! You now know how to write TLDR tests.
105
+ And that's it! You officially know how to write TLDR tests.
104
106
 
105
107
  ## Running your tests
106
108
 
107
- Because TLDR ships with a CLI, it offers a _lot_ of ways to run your tests.
109
+ Because TLDR ships with a CLI, it offers a veritable _plethora_ of ways to run your tests.
108
110
 
109
111
  ### Running your tests
110
112
 
@@ -120,7 +122,7 @@ This assumes your tests are stored in `test/`. It will also add `lib/` to Ruby's
120
122
 
121
123
  TLDR ships with a minimal [rake task](lib/tldr/rake.rb) that simply shells out to the `tldr` CLI by default. If you want to run TLDR with Rake, you can configure the task by setting flags on an env var named `TLDR_OPTS` or in a [.tldr.yml file](#setting-defaults-in-tldryml).
122
124
 
123
- All your Rakefile needs is `require "tldr/rake` and you can run the task individually like this:
125
+ All your Rakefile needs is `require "tldr/rake"` and you can run the task individually like this:
124
126
 
125
127
  ```
126
128
  $ rake tldr
@@ -138,9 +140,9 @@ require "tldr/rake"
138
140
  task default: ["tldr", "standard:fix"]
139
141
  ```
140
142
 
141
- One reason you'd want to invoke TLDR with Rake is because you have multiple test suites that you want to be able to conveniently run separately ([this talk](https://blog.testdouble.com/talks/2014-05-25-breaking-up-with-your-test-suite/) discussed a few reasons why this can be useful).
143
+ One situation where you'd want to invoke TLDR with Rake is when you have multiple test suites that you want to be able to easily run separately ([this talk](https://blog.testdouble.com/talks/2014-05-25-breaking-up-with-your-test-suite/) discussed a few reasons why this can be useful).
142
144
 
143
- To create a custom TLDR Rake task, you can instantiate `TLDR::Task` like this, which allows you to configure [TLDR::Config](/lib/tldr/value/config.rb) in code:
145
+ To create a custom TLDR Rake task, you can instantiate `TLDR::Task` like this, which allows you to define its [TLDR::Config](/lib/tldr/value/config.rb) configuration in code:
144
146
 
145
147
  ```ruby
146
148
  require "tldr/rake"
@@ -258,6 +260,35 @@ timeout: 0.01
258
260
 
259
261
  And if you're running with the timeout enabled this way, you can still disable it for any given test run by adding the `--no-timeout` flag.
260
262
 
263
+ #### Consider timeouts as a success with exit code 0
264
+
265
+ By default, when TLDR times out it exits with status code 3 to indicate the test suite was aborted. However, if you know that your full test suite is slower than whatever duration you consider to be "fast enough for fast feedback", you can use the `--exit-0-on-timeout` flag and simply use the timeout to enforce whatever feedback loop budget you set with `--timeout`.
266
+
267
+ For example:
268
+
269
+ ```
270
+ # Get feedback within 400ms but don't consider a timeout a failure
271
+ tldr --timeout 0.4 --exit-0-on-timeout
272
+ ```
273
+
274
+ This is particularly useful for:
275
+ - [Claude Code hooks](https://docs.anthropic.com/en/docs/claude-code/hooks) where you want fast feedback without failing the command
276
+ - [Git pre-commit hooks](https://git-scm.com/docs/githooks#_pre_commit) where you want quick test results without blocking commits
277
+ - Development workflows where timeouts are informational rather than failures
278
+ - Any situation where you want a time budget without hard failures
279
+
280
+ ## Using TLDR as a Claude Code hook
281
+
282
+ In AI agent-based coding workflows, it's common to [configure hooks](https://docs.anthropic.com/en/docs/claude-code/hooks) that will block the agent from proceeding whenever tests or linters fail. With Claude Code specifically, a hook will only block if it exits with status code 2. As a result, you can use `--exit-2-on-failure` so that assertion failures will block the agent from continuing.
283
+
284
+ So you might configure a Claude Code hook with a 250ms budget by setting timeouts to exit code 0 and failures to exit code 2:
285
+
286
+ ```
287
+ # Get feedback within 250ms, don't block the agent on timeouts, but do block it on assertion failures
288
+ tldr --timeout 0.25 --exit-0-on-timeout --exit-2-on-failure
289
+ ```
290
+
291
+
261
292
  ## Questions you might be asking
262
293
 
263
294
  TLDR is very similar to Minitest in API, but different in enough ways that you
@@ -106,6 +106,14 @@ class TLDR
106
106
  options[:yes_i_know] = true
107
107
  end
108
108
 
109
+ opts.on CONFLAGS[:exit_0_on_timeout], "Exit with status code 0 when suite times out instead of 3" do
110
+ options[:exit_0_on_timeout] = true
111
+ end
112
+
113
+ opts.on CONFLAGS[:exit_2_on_failure], "Exit with status code 2 (normally for errors) for both failures and errors" do
114
+ options[:exit_2_on_failure] = true
115
+ end
116
+
109
117
  opts.on CONFLAGS[:print_interrupted_test_backtraces], "Print stack traces of tests interrupted after a timeout" do |print_interrupted_test_backtraces|
110
118
  options[:print_interrupted_test_backtraces] = print_interrupted_test_backtraces
111
119
  end
data/lib/tldr/runner.rb CHANGED
@@ -40,7 +40,13 @@ class TLDR
40
40
  @run_aborted.make_true
41
41
  @wip.each(&:capture_backtrace_at_exit)
42
42
  reporter.after_tldr(plan.tests, @wip.dup, @results.dup) if reporter.respond_to?(:after_tldr)
43
- exit!(3)
43
+
44
+ # If there are failures/errors, use their exit code regardless of exit_0_on_timeout
45
+ if @results.any? { |result| result.error? || result.failure? }
46
+ exit!(exit_code(@results, config))
47
+ else
48
+ exit!(config.exit_0_on_timeout ? 0 : 3)
49
+ end
44
50
  end
45
51
 
46
52
  sleep(config.timeout)
@@ -62,7 +68,7 @@ class TLDR
62
68
 
63
69
  unless @run_aborted.true?
64
70
  reporter.after_suite(results) if reporter.respond_to?(:after_suite)
65
- exit(exit_code(results))
71
+ exit(exit_code(results, config))
66
72
  end
67
73
  end
68
74
 
@@ -119,11 +125,11 @@ class TLDR
119
125
  ((Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond) - start) / 1000.0).round
120
126
  end
121
127
 
122
- def exit_code results
128
+ def exit_code results, config
123
129
  if results.any? { |result| result.error? }
124
130
  2
125
131
  elsif results.any? { |result| result.failure? }
126
- 1
132
+ config.exit_2_on_failure ? 2 : 1
127
133
  else
128
134
  0
129
135
  end
@@ -22,6 +22,8 @@ class TLDR
22
22
  yes_i_know: "--yes-i-know",
23
23
  print_interrupted_test_backtraces: "--print-interrupted-test-backtraces",
24
24
  i_am_being_watched: "--i-am-being-watched",
25
+ exit_0_on_timeout: "--exit-0-on-timeout",
26
+ exit_2_on_failure: "--exit-2-on-failure",
25
27
  paths: nil
26
28
  }.freeze
27
29
 
@@ -32,7 +34,7 @@ class TLDR
32
34
  :exclude_paths, :helper_paths, :no_helper, :prepend_paths, :no_prepend,
33
35
  :load_paths, :base_path, :config_path, :reporter, :emoji, :warnings,
34
36
  :verbose, :yes_i_know, :print_interrupted_test_backtraces,
35
- :i_am_being_watched, :paths,
37
+ :i_am_being_watched, :exit_0_on_timeout, :exit_2_on_failure, :paths,
36
38
  # Internal properties
37
39
  :config_intended_for_merge_only, :seed_set_intentionally, :cli_defaults
38
40
  ].freeze
@@ -79,7 +81,9 @@ class TLDR
79
81
  verbose: false,
80
82
  yes_i_know: false,
81
83
  print_interrupted_test_backtraces: false,
82
- i_am_being_watched: false
84
+ i_am_being_watched: false,
85
+ exit_0_on_timeout: false,
86
+ exit_2_on_failure: false
83
87
  }
84
88
 
85
89
  if cli_defaults
@@ -118,7 +122,7 @@ class TLDR
118
122
  end
119
123
 
120
124
  # Booleans
121
- [:watch, :fail_fast, :parallel, :no_helper, :no_prepend, :emoji, :warnings, :verbose, :yes_i_know, :print_interrupted_test_backtraces, :i_am_being_watched].each do |key|
125
+ [:watch, :fail_fast, :parallel, :no_helper, :no_prepend, :emoji, :warnings, :verbose, :yes_i_know, :print_interrupted_test_backtraces, :i_am_being_watched, :exit_0_on_timeout, :exit_2_on_failure].each do |key|
122
126
  merged_args[key] = defaults[key] if merged_args[key].nil?
123
127
  end
124
128
 
@@ -10,7 +10,7 @@ class TLDR
10
10
 
11
11
  # Test exact match starting line condition first to save us a potential re-parsing to look up end_line
12
12
  def covers_line? l
13
- line == l || (l >= line && l <= end_line)
13
+ line == l || l.between?(line, end_line)
14
14
  end
15
15
 
16
16
  def group?
data/lib/tldr/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class TLDR
2
- VERSION = "1.0.0"
2
+ VERSION = "1.1.0"
3
3
  end
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: 1.0.0
4
+ version: 1.1.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: 2025-04-03 00:00:00.000000000 Z
12
+ date: 2025-07-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: super_diff
@@ -130,7 +130,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
130
130
  - !ruby/object:Gem::Version
131
131
  version: '0'
132
132
  requirements: []
133
- rubygems_version: 3.3.27
133
+ rubygems_version: 3.3.26
134
134
  signing_key:
135
135
  specification_version: 4
136
136
  summary: TLDR will run your tests, but only for 1.8 seconds.