├── .github
    └── workflows
    │   └── main.yml
├── .gitignore
├── .standard.yml
├── CHANGELOG.md
├── Gemfile
├── Gemfile.lock
├── LICENSE.txt
├── README.md
├── Rakefile
├── bin
    ├── console
    └── setup
├── example
    ├── a
    │   ├── Gemfile
    │   ├── Gemfile.lock
    │   ├── config
    │   │   └── TldrFile
    │   └── test
    │   │   ├── add_test.rb
    │   │   ├── helper.rb
    │   │   └── test_subtract.rb
    ├── b
    │   ├── Gemfile
    │   ├── Gemfile.lock
    │   ├── Rakefile
    │   ├── lib
    │   │   └── my_lib.rb
    │   ├── safe
    │   │   ├── big_test.rb
    │   │   └── helper.rb
    │   └── test
    │   │   ├── helper.rb
    │   │   └── some_test.rb
    ├── c
    │   ├── .tldr.yml
    │   ├── Gemfile
    │   ├── Gemfile.lock
    │   ├── Rakefile
    │   ├── bin
    │   │   └── tapioca
    │   ├── sorbet
    │   │   ├── config
    │   │   ├── rbi
    │   │   │   └── gems
    │   │   │   │   ├── .gitattributes
    │   │   │   │   ├── attr_extras@7.1.0.rbi
    │   │   │   │   ├── concurrent-ruby@1.2.2.rbi
    │   │   │   │   ├── diff-lcs@1.5.0.rbi
    │   │   │   │   ├── erubi@1.12.0.rbi
    │   │   │   │   ├── netrc@0.11.0.rbi
    │   │   │   │   ├── optimist@3.1.0.rbi
    │   │   │   │   ├── parallel@1.23.0.rbi
    │   │   │   │   ├── patience_diff@1.2.0.rbi
    │   │   │   │   ├── prettier_print@1.2.1.rbi
    │   │   │   │   ├── rake@13.0.6.rbi
    │   │   │   │   ├── rbi@0.1.1.rbi
    │   │   │   │   ├── spoom@1.2.4.rbi
    │   │   │   │   ├── super_diff@0.10.0.rbi
    │   │   │   │   ├── syntax_tree@6.2.0.rbi
    │   │   │   │   ├── tapioca@0.11.9.rbi
    │   │   │   │   ├── thor@1.2.2.rbi
    │   │   │   │   ├── tldr@0.9.3.rbi
    │   │   │   │   ├── yard-sorbet@0.8.1.rbi
    │   │   │   │   ├── yard@0.9.34.rbi
    │   │   │   │   └── yarp@0.13.0.rbi
    │   │   └── tapioca
    │   │   │   ├── config.yml
    │   │   │   └── require.rb
    │   └── spec
    │   │   ├── math_spec.rb
    │   │   └── spec_helper.rb
    └── d
    │   ├── .tldr.yml
    │   ├── Gemfile
    │   ├── Gemfile.lock
    │   └── b.rb
├── exe
    ├── tldr
    └── tldt
├── lib
    ├── tldr.rb
    └── tldr
    │   ├── argv_parser.rb
    │   ├── argv_reconstructor.rb
    │   ├── assertions.rb
    │   ├── autorun.rb
    │   ├── backtrace_filter.rb
    │   ├── class_util.rb
    │   ├── error.rb
    │   ├── executor.rb
    │   ├── hooks.rb
    │   ├── minitest_compatibility.rb
    │   ├── parallel_controls.rb
    │   ├── path_util.rb
    │   ├── planner.rb
    │   ├── rake.rb
    │   ├── reporters.rb
    │   ├── reporters
    │       ├── base.rb
    │       ├── default.rb
    │       └── icon_provider.rb
    │   ├── ruby_util.rb
    │   ├── runner.rb
    │   ├── skippable.rb
    │   ├── sorbet_compatibility.rb
    │   ├── strategizer.rb
    │   ├── value.rb
    │   ├── value
    │       ├── config.rb
    │       ├── location.rb
    │       ├── plan.rb
    │       ├── test.rb
    │       ├── test_group.rb
    │       ├── test_result.rb
    │       └── wip_test.rb
    │   ├── version.rb
    │   ├── watcher.rb
    │   └── yaml_parser.rb
├── script
    ├── setup
    ├── test
    └── upgrade
├── tests
    ├── api_driver_test.rb
    ├── at_exit_driver_test.rb
    ├── autorun_test.rb
    ├── backtrace_filter_test.rb
    ├── base_path_test.rb
    ├── custom_reporter_test.rb
    ├── directory_paths_test.rb
    ├── dont_run_these_in_parallel_test.rb
    ├── dotfile_test.rb
    ├── driver
    │   ├── api_driver.rb
    │   └── at_exit_driver.rb
    ├── emoji_test.rb
    ├── exclude_names_test.rb
    ├── exclude_path_test.rb
    ├── exit_code_test.rb
    ├── fail_fast_test.rb
    ├── fixture
    │   ├── autorun.rb
    │   ├── c.rb
    │   ├── custom_reporter.rb
    │   ├── custom_reporter_helper.rb
    │   ├── diffs.rb
    │   ├── dont_run_these_in_parallel.rb
    │   ├── dont_run_these_in_parallel_superclasses.rb
    │   ├── emoji.rb
    │   ├── error.rb
    │   ├── fail.rb
    │   ├── fail_fast.rb
    │   ├── folder
    │   │   ├── a.rb
    │   │   └── b.rb
    │   ├── helper_a.rb
    │   ├── helper_b.rb
    │   ├── hooks.rb
    │   ├── line_number.rb
    │   ├── name_filters.rb
    │   ├── parallel.rb
    │   ├── run_these_together.rb
    │   ├── run_these_together_superclasses.rb
    │   ├── seed.rb
    │   ├── several_fails.rb
    │   ├── skip.rb
    │   ├── slow.rb
    │   ├── subsubclass.rb
    │   ├── success.rb
    │   ├── suite_summary.rb
    │   ├── suite_summary_too_slow.rb
    │   └── warnings.rb
    ├── helpers_test.rb
    ├── hooks_test.rb
    ├── line_number_test.rb
    ├── name_filters_test.rb
    ├── prepend_test.rb
    ├── rake_task_test.rb
    ├── run_these_together_test.rb
    ├── seed_test.rb
    ├── subsubclass_test.rb
    ├── suite_summary_test.rb
    ├── test_helper.rb
    ├── tldr
    │   ├── argv_parser_test.rb
    │   ├── assertions_test.rb
    │   ├── minitest_compatibility_test.rb
    │   ├── reporters
    │   │   └── default_test.rb
    │   ├── strategizer_test.rb
    │   ├── value
    │   │   ├── config_test.rb
    │   │   └── test_test.rb
    │   └── yaml_parser_test.rb
    ├── tldr_test.rb
    ├── warnings_test.rb
    └── wip_test_test.rb
└── tldr.gemspec
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
 1 | name: Ruby
 2 | 
 3 | on:
 4 |   push:
 5 |     branches:
 6 |       - main
 7 | 
 8 |   pull_request:
 9 | 
10 | jobs:
11 |   build:
12 |     runs-on: ubuntu-latest
13 |     name: Ruby ${{ matrix.ruby }}
14 |     strategy:
15 |       matrix:
16 |         ruby: ['3.1', '3.2', '3.3', '3.4']
17 | 
18 |     steps:
19 |     - uses: actions/checkout@v3
20 |     - name: Set up Ruby
21 |       uses: ruby/setup-ruby@v1
22 |       with:
23 |         ruby-version: ${{ matrix.ruby }}
24 |         bundler-cache: false
25 |     - name: Run setup script
26 |       run: script/setup
27 |     - name: Run tests
28 |       run: script/test
29 | 
30 | 
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.bundle/
2 | /.yardoc
3 | /_yardoc/
4 | /coverage/
5 | /doc/
6 | /pkg/
7 | /spec/reports/
8 | /tmp/
9 | 
--------------------------------------------------------------------------------
/.standard.yml:
--------------------------------------------------------------------------------
1 | # For available configuration options, see:
2 | #   https://github.com/testdouble/standard
3 | ruby_version: 3.2
4 | 
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
  1 | ## unreleased
  2 | 
  3 | ## [1.1.1]
  4 | 
  5 | * Allow empty config files [#21](https://github.com/tendersearls/tldr/pull/21)
  6 | 
  7 | ## [1.1.0]
  8 | 
  9 | * 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")
 10 | * 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
 11 | 
 12 | ## [1.0.0]
 13 | 
 14 | * **BREAKING** you know how the whole point of TLDR is that it aborts your test
 15 | run after 1.8s? Yeah, well, it doesn't anymore. Use `--timeout` to enable it
 16 | * **BREAKING** replace the `--no-dotfile` flag and has been replaced by a
 17 | `--[no-]config PATH` flag. To skip loading the YAML file, use `--no-config`.
 18 | To set the file, use `--config FILE` option
 19 | * **BREAKING** Remove `assert_include?` and `refute_include?` in favor of
 20 | Minitest-compatible `assert_includes` and `refute_includes`.
 21 | * **BREAKING** Rename `TLDR::Assertions::MinitestCompatibility` to `TLDR::MinitestCompatibility` and remove `assert_send`, which [nobody uses](https://github.com/minitest/minitest/issues/668)
 22 | * **BREAKING** Replace `no_emoji` YAML option with `emoji` option. Disable emoji output by default. Add `--emoji` flag for enabling it.
 23 | * Add `--[no-]timeout TIMEOUT` flag and `timeout` YAML option. To enable the
 24 | TLDR Classic™ default of 1.8s, specify `--timeout` from the CLI or `timeout: true`
 25 | in YAML. To specify a custom timeout of 42.3 seconds, flag `--timeout 42.3` or
 26 | `timeout: 42.3` in YAML
 27 | * Add `require "tldr/autorun"`, which adds an `at_exit` hook so that tests can be
 28 | run from the command line (still supports CLI args and YAML options) by running `ruby path/to/test.rb` (see [its test](/tests/autorun_test.rb))
 29 | * Fix custom reporters by looking them up only after helpers have a chance to run. [#15](https://github.com/tendersearls/tldr/issues/15)
 30 | 
 31 | ## [0.10.1]
 32 | 
 33 | * Fix 3.4 / Prism [#17](https://github.com/tendersearls/tldr/pull/17)
 34 | 
 35 | ## [0.10.0]
 36 | 
 37 | * Add an option to print the stack traces of interrupted tests [#13](https://github.com/tendersearls/tldr/pull/13) by [@henrahmagix](https://github.com/henrahmagix)
 38 | 
 39 | ## [0.9.5]
 40 | 
 41 | * Fix warning when defining `setup`/`teardown` on TLDR class itself [#7](https://github.com/tendersearls/tldr/issues/7)
 42 | 
 43 | ## [0.9.4]
 44 | 
 45 | * Fix Sorbet compatibility [#5](https://github.com/tendersearls/tldr/issues/5)
 46 | 
 47 | ## [0.9.3]
 48 | 
 49 | * Print how many tests ran vs. didn't even when suppressing TLDR summary
 50 | 
 51 | ## [0.9.2]
 52 | 
 53 | * Don't redundantly print out dotfile config values for re-run instructions
 54 | 
 55 | ## [0.9.1]
 56 | 
 57 | * Correctly clear the screen between runs
 58 | 
 59 | ## [0.9.0]
 60 | 
 61 | * Add a `--watch` option that will spawn fswatch | xargs and clear the screen
 62 | between runs (requires fswatch to gbe installed)
 63 | * Add "lib" as a default load path along with "test"
 64 | 
 65 | ## [0.8.0]
 66 | 
 67 | * Add a `--yes-i-know` flag that will suppress the large warning when your test
 68 | suite runs over the 1.8s limit
 69 | 
 70 | ## [0.7.0]
 71 | 
 72 | * Add a `tldt` alias for folks who have another executable named `tldr` on their
 73 | paths
 74 | * BREAKING: Reverse decision in 0.1.1 to capture_io on every TLDR subclass;
 75 | moving back to the MinitestCompatibility mixin
 76 | * Fix `assert_in_delta` defaultarg to retain Minitest compatibility
 77 | * Add `mu_pp` to the MinitestCompatibility mixin
 78 | 
 79 | ## [0.6.2]
 80 | 
 81 | * Since TLDR runs fine on 3.1, reduce the gem requirement
 82 | 
 83 | ## [0.6.1]
 84 | 
 85 | * Correctly report the number of test classes that run
 86 | * Finish planning the test run before starting the clock on the timer (that's
 87 | a millisecond or two in savings!)
 88 | 
 89 | ## [0.6.0]
 90 | 
 91 | * When `dont_run_these_in_parallel!` and `run_these_together!` are called from a
 92 | super class, gather subclasses' methods as well when the method is `nil`
 93 | * Stop shelling out to `tldr` from our Rake task. Rescue `SystemExit` instead
 94 | * Rename `Config#helper` to `Config#helper_paths`, which YAML config keys
 95 | * Print Ruby warnings by default (disable with --no-warnings)
 96 | 
 97 | ## [0.5.0]
 98 | 
 99 | * Define your own Rake tasks with `TLDR::Task` and pass in a custom configuration
100 | * Any tests with `--prepend` AND marked thread-unsafe with `dont_run_these_in_parallel`
101 | will be run BEFORE the parallel tests start running. This way if you're working
102 | on a non-parallelizable test, running `tldr` will automatically run it first
103 | thing
104 | * Stop printing `--seed` in run commands, since it can be confusing to discover
105 | that will disable `--parallel`. Instead, print the seed option beneath
106 | 
107 | ## [0.4.0]
108 | 
109 | * Add `TLDR.dont_run_these_in_parallel!` method to allow tests to indicate that they
110 | must be run in isolation and not concurrently with any other tests
111 | * Add support for `around` hooks (similar to [minitest-around](https://github.com/splattael/minitest-around))
112 | 
113 | ## [0.3.0]
114 | 
115 | * Add `TLDR.run_these_together!` method to allow tests that can't safely be run
116 | concurrently to be grouped and run together
117 | 
118 | ## [0.2.1]
119 | 
120 | * Define a default empty setup/teardown in the base class, to guard against
121 | users getting `super: no superclass method `setup'` errors when they dutifully
122 | call super from their hooks
123 | 
124 | ## [0.2.0]
125 | 
126 | - Add a rake task "tldr"
127 | ## [0.1.1]
128 | 
129 | - Improve Minitest compatibility by mixing in Assertions#capture_io
130 | - Fix whitespace in reporter
131 | 
132 | ## [0.1.0]
133 | 
134 | - Initial release
135 | 
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 | 
3 | gemspec
4 | 
5 | gem "rake"
6 | gem "minitest"
7 | gem "m"
8 | gem "standard"
9 | 
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
  1 | PATH
  2 |   remote: .
  3 |   specs:
  4 |     tldr (1.1.1)
  5 |       concurrent-ruby (~> 1.2)
  6 |       irb (~> 1.10)
  7 |       super_diff (~> 0.10)
  8 | 
  9 | GEM
 10 |   remote: https://rubygems.org/
 11 |   specs:
 12 |     ast (2.4.3)
 13 |     attr_extras (7.1.0)
 14 |     cgi (0.5.0)
 15 |     concurrent-ruby (1.3.5)
 16 |     date (3.4.1)
 17 |     diff-lcs (1.6.2)
 18 |     erb (4.0.4)
 19 |       cgi (>= 0.3.3)
 20 |     io-console (0.8.0)
 21 |     irb (1.15.2)
 22 |       pp (>= 0.6.0)
 23 |       rdoc (>= 4.0.0)
 24 |       reline (>= 0.4.2)
 25 |     json (2.12.2)
 26 |     language_server-protocol (3.17.0.5)
 27 |     lint_roller (1.1.0)
 28 |     m (1.6.2)
 29 |       method_source (>= 0.6.7)
 30 |       rake (>= 0.9.2.2)
 31 |     method_source (1.1.0)
 32 |     minitest (5.25.5)
 33 |     optimist (3.2.1)
 34 |     parallel (1.27.0)
 35 |     parser (3.3.8.0)
 36 |       ast (~> 2.4.1)
 37 |       racc
 38 |     patience_diff (1.2.0)
 39 |       optimist (~> 3.0)
 40 |     pp (0.6.2)
 41 |       prettyprint
 42 |     prettyprint (0.2.0)
 43 |     prism (1.4.0)
 44 |     psych (5.2.6)
 45 |       date
 46 |       stringio
 47 |     racc (1.8.1)
 48 |     rainbow (3.1.1)
 49 |     rake (13.3.0)
 50 |     rdoc (6.14.2)
 51 |       erb
 52 |       psych (>= 4.0.0)
 53 |     regexp_parser (2.10.0)
 54 |     reline (0.6.1)
 55 |       io-console (~> 0.5)
 56 |     rubocop (1.75.8)
 57 |       json (~> 2.3)
 58 |       language_server-protocol (~> 3.17.0.2)
 59 |       lint_roller (~> 1.1.0)
 60 |       parallel (~> 1.10)
 61 |       parser (>= 3.3.0.2)
 62 |       rainbow (>= 2.2.2, < 4.0)
 63 |       regexp_parser (>= 2.9.3, < 3.0)
 64 |       rubocop-ast (>= 1.44.0, < 2.0)
 65 |       ruby-progressbar (~> 1.7)
 66 |       unicode-display_width (>= 2.4.0, < 4.0)
 67 |     rubocop-ast (1.45.1)
 68 |       parser (>= 3.3.7.2)
 69 |       prism (~> 1.4)
 70 |     rubocop-performance (1.25.0)
 71 |       lint_roller (~> 1.1)
 72 |       rubocop (>= 1.75.0, < 2.0)
 73 |       rubocop-ast (>= 1.38.0, < 2.0)
 74 |     ruby-progressbar (1.13.0)
 75 |     standard (1.50.0)
 76 |       language_server-protocol (~> 3.17.0.2)
 77 |       lint_roller (~> 1.0)
 78 |       rubocop (~> 1.75.5)
 79 |       standard-custom (~> 1.0.0)
 80 |       standard-performance (~> 1.8)
 81 |     standard-custom (1.0.2)
 82 |       lint_roller (~> 1.0)
 83 |       rubocop (~> 1.50)
 84 |     standard-performance (1.8.0)
 85 |       lint_roller (~> 1.1)
 86 |       rubocop-performance (~> 1.25.0)
 87 |     stringio (3.1.7)
 88 |     super_diff (0.16.0)
 89 |       attr_extras (>= 6.2.4)
 90 |       diff-lcs
 91 |       patience_diff
 92 |     unicode-display_width (3.1.4)
 93 |       unicode-emoji (~> 4.0, >= 4.0.4)
 94 |     unicode-emoji (4.0.4)
 95 | 
 96 | PLATFORMS
 97 |   arm64-darwin-23
 98 |   arm64-darwin-24
 99 | 
100 | DEPENDENCIES
101 |   m
102 |   minitest
103 |   rake
104 |   standard
105 |   tldr!
106 | 
107 | BUNDLED WITH
108 |    2.4.17
109 | 
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
 1 | The MIT License (MIT)
 2 | 
 3 | Copyright (c) 2023 Aaron Patterson, Justin Searls
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy
 6 | of this software and associated documentation files (the "Software"), to deal
 7 | in the Software without restriction, including without limitation the rights
 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 | 
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 | 
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | require "bundler/gem_tasks"
 4 | require "rake/testtask"
 5 | 
 6 | Rake::TestTask.new(:test) do |t|
 7 |   t.libs << "tests"
 8 |   t.libs << "lib"
 9 |   t.warning = true
10 |   t.test_files = FileList["tests/**/*_test.rb"]
11 | end
12 | 
13 | require "standard/rake"
14 | 
15 | task default: %i[test standard:fix]
16 | 
--------------------------------------------------------------------------------
/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | 
3 | require "bundler/setup"
4 | require "tldr"
5 | 
6 | require "irb"
7 | IRB.start(__FILE__)
8 | 
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 | IFS=$'\n\t'
4 | set -vx
5 | 
6 | bundle install
7 | 
--------------------------------------------------------------------------------
/example/a/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 | 
3 | gem "tldr", path: "../.."
4 | 
--------------------------------------------------------------------------------
/example/a/Gemfile.lock:
--------------------------------------------------------------------------------
 1 | PATH
 2 |   remote: ../..
 3 |   specs:
 4 |     tldr (1.1.1)
 5 |       concurrent-ruby (~> 1.2)
 6 |       irb (~> 1.10)
 7 |       super_diff (~> 0.10)
 8 | 
 9 | GEM
10 |   remote: https://rubygems.org/
11 |   specs:
12 |     attr_extras (7.1.0)
13 |     cgi (0.5.0)
14 |     concurrent-ruby (1.3.5)
15 |     date (3.4.1)
16 |     diff-lcs (1.6.2)
17 |     erb (4.0.4)
18 |       cgi (>= 0.3.3)
19 |     io-console (0.8.0)
20 |     irb (1.15.2)
21 |       pp (>= 0.6.0)
22 |       rdoc (>= 4.0.0)
23 |       reline (>= 0.4.2)
24 |     optimist (3.2.1)
25 |     patience_diff (1.2.0)
26 |       optimist (~> 3.0)
27 |     pp (0.6.2)
28 |       prettyprint
29 |     prettyprint (0.2.0)
30 |     psych (5.2.6)
31 |       date
32 |       stringio
33 |     rdoc (6.14.2)
34 |       erb
35 |       psych (>= 4.0.0)
36 |     reline (0.6.1)
37 |       io-console (~> 0.5)
38 |     stringio (3.1.7)
39 |     super_diff (0.16.0)
40 |       attr_extras (>= 6.2.4)
41 |       diff-lcs
42 |       patience_diff
43 | 
44 | PLATFORMS
45 |   arm64-darwin-22
46 |   arm64-darwin-23
47 |   arm64-darwin-24
48 | 
49 | DEPENDENCIES
50 |   tldr!
51 | 
52 | BUNDLED WITH
53 |    2.4.17
54 | 
--------------------------------------------------------------------------------
/example/a/config/TldrFile:
--------------------------------------------------------------------------------
1 | paths:
2 |   - "test/**/test_*.rb"
3 | 
--------------------------------------------------------------------------------
/example/a/test/add_test.rb:
--------------------------------------------------------------------------------
1 | class AddTest < TLDR
2 |   some_user_class_method!
3 | 
4 |   def test_add
5 |     assert_equal 2, 1 + 1
6 |   end
7 | end
8 | 
--------------------------------------------------------------------------------
/example/a/test/helper.rb:
--------------------------------------------------------------------------------
 1 | class TLDR
 2 |   def setup
 3 |   end
 4 | 
 5 |   def teardown
 6 |   end
 7 | 
 8 |   def self.some_user_class_method!
 9 |   end
10 | end
11 | 
--------------------------------------------------------------------------------
/example/a/test/test_subtract.rb:
--------------------------------------------------------------------------------
1 | class TestSubtract < TLDR
2 |   def test_subtract
3 |     assert_equal 2, 3 - 1
4 |   end
5 | end
6 | 
--------------------------------------------------------------------------------
/example/b/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 | 
3 | gem "rake"
4 | gem "tldr", path: "../.."
5 | gem "mocktail"
6 | 
--------------------------------------------------------------------------------
/example/b/Gemfile.lock:
--------------------------------------------------------------------------------
 1 | PATH
 2 |   remote: ../..
 3 |   specs:
 4 |     tldr (1.1.1)
 5 |       concurrent-ruby (~> 1.2)
 6 |       irb (~> 1.10)
 7 |       super_diff (~> 0.10)
 8 | 
 9 | GEM
10 |   remote: https://rubygems.org/
11 |   specs:
12 |     attr_extras (7.1.0)
13 |     cgi (0.5.0)
14 |     concurrent-ruby (1.3.5)
15 |     date (3.4.1)
16 |     diff-lcs (1.6.2)
17 |     erb (4.0.4)
18 |       cgi (>= 0.3.3)
19 |     io-console (0.8.0)
20 |     irb (1.15.2)
21 |       pp (>= 0.6.0)
22 |       rdoc (>= 4.0.0)
23 |       reline (>= 0.4.2)
24 |     mocktail (2.0.0)
25 |       sorbet-eraser (~> 0.3.1)
26 |       sorbet-runtime (~> 0.5.9204)
27 |     optimist (3.2.1)
28 |     patience_diff (1.2.0)
29 |       optimist (~> 3.0)
30 |     pp (0.6.2)
31 |       prettyprint
32 |     prettyprint (0.2.0)
33 |     psych (5.2.6)
34 |       date
35 |       stringio
36 |     rake (13.3.0)
37 |     rdoc (6.14.2)
38 |       erb
39 |       psych (>= 4.0.0)
40 |     reline (0.6.1)
41 |       io-console (~> 0.5)
42 |     sorbet-eraser (0.3.1)
43 |     sorbet-runtime (0.5.12222)
44 |     stringio (3.1.7)
45 |     super_diff (0.16.0)
46 |       attr_extras (>= 6.2.4)
47 |       diff-lcs
48 |       patience_diff
49 | 
50 | PLATFORMS
51 |   arm64-darwin-22
52 |   arm64-darwin-23
53 |   arm64-darwin-24
54 | 
55 | DEPENDENCIES
56 |   mocktail
57 |   rake
58 |   tldr!
59 | 
60 | BUNDLED WITH
61 |    2.4.17
62 | 
--------------------------------------------------------------------------------
/example/b/Rakefile:
--------------------------------------------------------------------------------
 1 | require "tldr/rake"
 2 | 
 3 | TLDR::Task.new(name: :safe_tests, config: TLDR::Config.new(
 4 |   paths: FileList["safe/**/*_test.rb"],
 5 |   helper_paths: ["safe/helper.rb"],
 6 |   load_paths: ["lib", "safe"]
 7 | ))
 8 | 
 9 | task default: :tldr
10 | 
--------------------------------------------------------------------------------
/example/b/lib/my_lib.rb:
--------------------------------------------------------------------------------
1 | class MyLib
2 |   def return_stuff
3 |     :stuff
4 |   end
5 | end
6 | 
--------------------------------------------------------------------------------
/example/b/safe/big_test.rb:
--------------------------------------------------------------------------------
 1 | require "tldr"
 2 | 
 3 | require "my_lib"
 4 | 
 5 | class BigTest < TLDR
 6 |   print_cool!
 7 | 
 8 |   def setup
 9 |     @subject = MyLib.new
10 |   end
11 | 
12 |   def test_stuff
13 |     assert_equal :stuff, @subject.return_stuff
14 |   end
15 | end
16 | 
--------------------------------------------------------------------------------
/example/b/safe/helper.rb:
--------------------------------------------------------------------------------
1 | class TLDR
2 |   def self.print_cool!
3 |     puts "cool!"
4 |   end
5 | end
6 | 
--------------------------------------------------------------------------------
/example/b/test/helper.rb:
--------------------------------------------------------------------------------
1 | require "mocktail"
2 | 
3 | class TLDR
4 |   def self.print_neat!
5 |     puts "neat!"
6 |   end
7 | end
8 | 
--------------------------------------------------------------------------------
/example/b/test/some_test.rb:
--------------------------------------------------------------------------------
 1 | require "tldr"
 2 | TLDR::Run.at_exit!(TLDR::Config.new(fail_fast: true))
 3 | 
 4 | require "helper"
 5 | 
 6 | class SomeTest < TLDR
 7 |   print_neat!
 8 | 
 9 |   def test_truth
10 |     assert true
11 |   end
12 | end
13 | 
--------------------------------------------------------------------------------
/example/c/.tldr.yml:
--------------------------------------------------------------------------------
1 | paths:
2 |   - "spec/**/*_spec.rb"
3 | helper_paths:
4 |   - "spec/spec_helper.rb"
5 | 
--------------------------------------------------------------------------------
/example/c/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 | 
3 | gem "tldr", path: "../.."
4 | gem "rake"
5 | gem "sorbet"
6 | gem "sorbet-runtime"
7 | gem "tapioca", require: false
8 | 
--------------------------------------------------------------------------------
/example/c/Gemfile.lock:
--------------------------------------------------------------------------------
 1 | PATH
 2 |   remote: ../..
 3 |   specs:
 4 |     tldr (1.1.1)
 5 |       concurrent-ruby (~> 1.2)
 6 |       irb (~> 1.10)
 7 |       super_diff (~> 0.10)
 8 | 
 9 | GEM
10 |   remote: https://rubygems.org/
11 |   specs:
12 |     attr_extras (7.1.0)
13 |     benchmark (0.4.1)
14 |     cgi (0.5.0)
15 |     concurrent-ruby (1.3.5)
16 |     date (3.4.1)
17 |     diff-lcs (1.6.2)
18 |     erb (4.0.4)
19 |       cgi (>= 0.3.3)
20 |     erubi (1.13.1)
21 |     io-console (0.8.0)
22 |     irb (1.15.2)
23 |       pp (>= 0.6.0)
24 |       rdoc (>= 4.0.0)
25 |       reline (>= 0.4.2)
26 |     logger (1.7.0)
27 |     netrc (0.11.0)
28 |     optimist (3.2.1)
29 |     parallel (1.27.0)
30 |     patience_diff (1.2.0)
31 |       optimist (~> 3.0)
32 |     pp (0.6.2)
33 |       prettyprint
34 |     prettyprint (0.2.0)
35 |     prism (1.4.0)
36 |     psych (5.2.6)
37 |       date
38 |       stringio
39 |     rake (13.3.0)
40 |     rbi (0.3.6)
41 |       prism (~> 1.0)
42 |       rbs (>= 3.4.4)
43 |     rbs (3.9.4)
44 |       logger
45 |     rdoc (6.14.2)
46 |       erb
47 |       psych (>= 4.0.0)
48 |     reline (0.6.1)
49 |       io-console (~> 0.5)
50 |     sorbet (0.5.12222)
51 |       sorbet-static (= 0.5.12222)
52 |     sorbet-runtime (0.5.12222)
53 |     sorbet-static (0.5.12222-universal-darwin)
54 |     sorbet-static-and-runtime (0.5.12222)
55 |       sorbet (= 0.5.12222)
56 |       sorbet-runtime (= 0.5.12222)
57 |     spoom (1.6.1)
58 |       erubi (>= 1.10.0)
59 |       prism (>= 0.28.0)
60 |       rbi (>= 0.2.3)
61 |       sorbet-static-and-runtime (>= 0.5.10187)
62 |       thor (>= 0.19.2)
63 |     stringio (3.1.7)
64 |     super_diff (0.16.0)
65 |       attr_extras (>= 6.2.4)
66 |       diff-lcs
67 |       patience_diff
68 |     tapioca (0.16.11)
69 |       benchmark
70 |       bundler (>= 2.2.25)
71 |       netrc (>= 0.11.0)
72 |       parallel (>= 1.21.0)
73 |       rbi (~> 0.2)
74 |       sorbet-static-and-runtime (>= 0.5.11087)
75 |       spoom (>= 1.2.0)
76 |       thor (>= 1.2.0)
77 |       yard-sorbet
78 |     thor (1.3.2)
79 |     yard (0.9.37)
80 |     yard-sorbet (0.9.0)
81 |       sorbet-runtime
82 |       yard
83 | 
84 | PLATFORMS
85 |   arm64-darwin-22
86 |   arm64-darwin-23
87 |   arm64-darwin-24
88 | 
89 | DEPENDENCIES
90 |   rake
91 |   sorbet
92 |   sorbet-runtime
93 |   tapioca
94 |   tldr!
95 | 
96 | BUNDLED WITH
97 |    2.4.17
98 | 
--------------------------------------------------------------------------------
/example/c/Rakefile:
--------------------------------------------------------------------------------
1 | require "tldr/rake"
2 | 
3 | TLDR::Task.new(name: :b_tests, config: TLDR::Config.new(
4 |   base_path: "../b"
5 | ))
6 | 
7 | task default: :tldr
8 | 
--------------------------------------------------------------------------------
/example/c/bin/tapioca:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env ruby
 2 | # frozen_string_literal: true
 3 | 
 4 | #
 5 | # This file was generated by Bundler.
 6 | #
 7 | # The application 'tapioca' is installed as part of a gem, and
 8 | # this file is here to facilitate running it.
 9 | #
10 | 
11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12 | 
13 | bundle_binstub = File.expand_path("bundle", __dir__)
14 | 
15 | if File.file?(bundle_binstub)
16 |   if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
17 |     load(bundle_binstub)
18 |   else
19 |     abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
20 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
21 |   end
22 | end
23 | 
24 | require "rubygems"
25 | require "bundler/setup"
26 | 
27 | load Gem.bin_path("tapioca", "tapioca")
28 | 
--------------------------------------------------------------------------------
/example/c/sorbet/config:
--------------------------------------------------------------------------------
1 | --dir
2 | .
3 | --ignore=tmp/
4 | --ignore=vendor/
5 | 
--------------------------------------------------------------------------------
/example/c/sorbet/rbi/gems/.gitattributes:
--------------------------------------------------------------------------------
1 | **/*.rbi linguist-generated=true
2 | 
--------------------------------------------------------------------------------
/example/c/sorbet/rbi/gems/attr_extras@7.1.0.rbi:
--------------------------------------------------------------------------------
  1 | # typed: true
  2 | 
  3 | # DO NOT EDIT MANUALLY
  4 | # This is an autogenerated file for types exported from the `attr_extras` gem.
  5 | # Please instead update this file by running `bin/tapioca gem attr_extras`.
  6 | 
  7 | # source://attr_extras//lib/attr_extras/version.rb#1
  8 | module AttrExtras
  9 |   class << self
 10 |     # source://attr_extras//lib/attr_extras/explicit.rb#12
 11 |     def mixin; end
 12 |   end
 13 | end
 14 | 
 15 | # source://attr_extras//lib/attr_extras/attr_implement.rb#1
 16 | class AttrExtras::AttrImplement
 17 |   # @return [AttrImplement] a new instance of AttrImplement
 18 |   #
 19 |   # source://attr_extras//lib/attr_extras/attr_implement.rb#2
 20 |   def initialize(klass, names); end
 21 | 
 22 |   # source://attr_extras//lib/attr_extras/attr_implement.rb#7
 23 |   def apply; end
 24 | end
 25 | 
 26 | # source://attr_extras//lib/attr_extras/params_builder.rb#2
 27 | class AttrExtras::AttrInitialize
 28 |   # @return [AttrInitialize] a new instance of AttrInitialize
 29 |   #
 30 |   # source://attr_extras//lib/attr_extras/attr_initialize.rb#4
 31 |   def initialize(klass, names, block); end
 32 | 
 33 |   # source://attr_extras//lib/attr_extras/attr_initialize.rb#13
 34 |   def apply; end
 35 | 
 36 |   private
 37 | 
 38 |   # Returns the value of attribute klass.
 39 |   #
 40 |   # source://attr_extras//lib/attr_extras/attr_initialize.rb#10
 41 |   def klass; end
 42 | 
 43 |   # Returns the value of attribute names.
 44 |   #
 45 |   # source://attr_extras//lib/attr_extras/attr_initialize.rb#10
 46 |   def names; end
 47 | 
 48 |   # source://attr_extras//lib/attr_extras/attr_initialize.rb#59
 49 |   def validate_args(values, klass_params); end
 50 | 
 51 |   # source://attr_extras//lib/attr_extras/attr_initialize.rb#49
 52 |   def validate_arity(provided_arity, klass); end
 53 | end
 54 | 
 55 | # source://attr_extras//lib/attr_extras/params_builder.rb#3
 56 | class AttrExtras::AttrInitialize::ParamsBuilder
 57 |   # @return [ParamsBuilder] a new instance of ParamsBuilder
 58 |   #
 59 |   # source://attr_extras//lib/attr_extras/params_builder.rb#6
 60 |   def initialize(names); end
 61 | 
 62 |   # source://attr_extras//lib/attr_extras/params_builder.rb#32
 63 |   def default_values; end
 64 | 
 65 |   # source://attr_extras//lib/attr_extras/params_builder.rb#17
 66 |   def hash_args; end
 67 | 
 68 |   # source://attr_extras//lib/attr_extras/params_builder.rb#23
 69 |   def hash_args_names; end
 70 | 
 71 |   # source://attr_extras//lib/attr_extras/params_builder.rb#27
 72 |   def hash_args_required; end
 73 | 
 74 |   # source://attr_extras//lib/attr_extras/params_builder.rb#13
 75 |   def positional_args; end
 76 | 
 77 |   private
 78 | 
 79 |   # Returns the value of attribute names.
 80 |   #
 81 |   # source://attr_extras//lib/attr_extras/params_builder.rb#10
 82 |   def names; end
 83 | 
 84 |   # source://attr_extras//lib/attr_extras/params_builder.rb#44
 85 |   def remove_required_sign(name); end
 86 | end
 87 | 
 88 | # source://attr_extras//lib/attr_extras/params_builder.rb#4
 89 | AttrExtras::AttrInitialize::ParamsBuilder::REQUIRED_SIGN = T.let(T.unsafe(nil), String)
 90 | 
 91 | # source://attr_extras//lib/attr_extras/attr_query.rb#1
 92 | module AttrExtras::AttrQuery
 93 |   class << self
 94 |     # source://attr_extras//lib/attr_extras/attr_query.rb#2
 95 |     def define_with_suffix(klass, suffix, *names); end
 96 |   end
 97 | end
 98 | 
 99 | # source://attr_extras//lib/attr_extras/attr_value.rb#1
100 | class AttrExtras::AttrValue
101 |   # @return [AttrValue] a new instance of AttrValue
102 |   #
103 |   # source://attr_extras//lib/attr_extras/attr_value.rb#2
104 |   def initialize(klass, *names); end
105 | 
106 |   # source://attr_extras//lib/attr_extras/attr_value.rb#10
107 |   def apply; end
108 | 
109 |   private
110 | 
111 |   # source://attr_extras//lib/attr_extras/attr_value.rb#22
112 |   def define_equals; end
113 | 
114 |   # source://attr_extras//lib/attr_extras/attr_value.rb#32
115 |   def define_hash_identity; end
116 | 
117 |   # source://attr_extras//lib/attr_extras/attr_value.rb#18
118 |   def define_readers; end
119 | 
120 |   # Returns the value of attribute klass.
121 |   #
122 |   # source://attr_extras//lib/attr_extras/attr_value.rb#7
123 |   def klass; end
124 | 
125 |   # Returns the value of attribute names.
126 |   #
127 |   # source://attr_extras//lib/attr_extras/attr_value.rb#7
128 |   def names; end
129 | end
130 | 
131 | # To avoid masking coding errors, we don't inherit from StandardError (which would be implicitly rescued). Forgetting to define a requisite method isn't just some runtime error.
132 | #
133 | # source://attr_extras//lib/attr_extras/explicit.rb#10
134 | class AttrExtras::MethodNotImplementedError < ::Exception; end
135 | 
136 | # Separate module so that mixing in the methods doesn't also mix in constants:
137 | # http://thepugautomatic.com/2014/02/private-api/
138 | #
139 | # source://attr_extras//lib/attr_extras/explicit.rb#18
140 | module AttrExtras::Mixin
141 |   # source://attr_extras//lib/attr_extras/explicit.rb#56
142 |   def aattr_initialize(*names, &block); end
143 | 
144 |   # source://attr_extras//lib/attr_extras/explicit.rb#56
145 |   def attr_accessor_initialize(*names, &block); end
146 | 
147 |   # source://attr_extras//lib/attr_extras/explicit.rb#89
148 |   def attr_id_query(*names); end
149 | 
150 |   # source://attr_extras//lib/attr_extras/explicit.rb#93
151 |   def attr_implement(*names); end
152 | 
153 |   # source://attr_extras//lib/attr_extras/explicit.rb#19
154 |   def attr_initialize(*names, &block); end
155 | 
156 |   # source://attr_extras//lib/attr_extras/explicit.rb#23
157 |   def attr_private(*names); end
158 | 
159 |   # source://attr_extras//lib/attr_extras/explicit.rb#35
160 |   def attr_private_initialize(*names, &block); end
161 | 
162 |   # source://attr_extras//lib/attr_extras/explicit.rb#85
163 |   def attr_query(*names); end
164 | 
165 |   # source://attr_extras//lib/attr_extras/explicit.rb#49
166 |   def attr_reader_initialize(*names, &block); end
167 | 
168 |   # source://attr_extras//lib/attr_extras/explicit.rb#31
169 |   def attr_value(*names); end
170 | 
171 |   # source://attr_extras//lib/attr_extras/explicit.rb#42
172 |   def attr_value_initialize(*names, &block); end
173 | 
174 |   # source://attr_extras//lib/attr_extras/explicit.rb#97
175 |   def cattr_implement(*names); end
176 | 
177 |   # source://attr_extras//lib/attr_extras/explicit.rb#81
178 |   def method_object(*names, &block); end
179 | 
180 |   # source://attr_extras//lib/attr_extras/explicit.rb#35
181 |   def pattr_initialize(*names, &block); end
182 | 
183 |   # source://attr_extras//lib/attr_extras/explicit.rb#49
184 |   def rattr_initialize(*names, &block); end
185 | 
186 |   # source://attr_extras//lib/attr_extras/explicit.rb#63
187 |   def static_facade(method_name_or_names, *names, &block); end
188 | 
189 |   # source://attr_extras//lib/attr_extras/explicit.rb#42
190 |   def vattr_initialize(*names, &block); end
191 | end
192 | 
193 | # source://attr_extras//lib/attr_extras/utils.rb#1
194 | module AttrExtras::Utils
195 |   class << self
196 |     # source://attr_extras//lib/attr_extras/utils.rb#2
197 |     def flat_names(names); end
198 |   end
199 | end
200 | 
201 | # source://attr_extras//lib/attr_extras/version.rb#2
202 | AttrExtras::VERSION = T.let(T.unsafe(nil), String)
203 | 
--------------------------------------------------------------------------------
/example/c/sorbet/rbi/gems/erubi@1.12.0.rbi:
--------------------------------------------------------------------------------
  1 | # typed: true
  2 | 
  3 | # DO NOT EDIT MANUALLY
  4 | # This is an autogenerated file for types exported from the `erubi` gem.
  5 | # Please instead update this file by running `bin/tapioca gem erubi`.
  6 | 
  7 | # source://erubi//lib/erubi.rb#3
  8 | module Erubi
  9 |   class << self
 10 |     def h(_arg0); end
 11 |   end
 12 | end
 13 | 
 14 | # source://erubi//lib/erubi.rb#54
 15 | class Erubi::Engine
 16 |   # Initialize a new Erubi::Engine.  Options:
 17 |   # +:bufval+ :: The value to use for the buffer variable, as a string (default '::String.new').
 18 |   # +:bufvar+ :: The variable name to use for the buffer variable, as a string.
 19 |   # +:chain_appends+ :: Whether to chain << calls to the buffer variable. Offers better
 20 |   #                     performance, but can cause issues when the buffer variable is reassigned during
 21 |   #                     template rendering (default +false+).
 22 |   # +:ensure+ :: Wrap the template in a begin/ensure block restoring the previous value of bufvar.
 23 |   # +:escapefunc+ :: The function to use for escaping, as a string (default: '::Erubi.h').
 24 |   # +:escape+ :: Whether to make <%= escape by default, and <%== not escape by default.
 25 |   # +:escape_html+ :: Same as +:escape+, with lower priority.
 26 |   # +:filename+ :: The filename for the template.
 27 |   #              the resulting source code.  Note this may cause problems if you are wrapping the resulting
 28 |   #              source code in other code, because the magic comment only has an effect at the beginning of
 29 |   #              the file, and having the magic comment later in the file can trigger warnings.
 30 |   # +:freeze_template_literals+ :: Whether to suffix all literal strings for template code with .freeze
 31 |   #                                (default: +true+ on Ruby 2.1+, +false+ on Ruby 2.0 and older).
 32 |   #                                Can be set to +false+ on Ruby 2.3+ when frozen string literals are enabled
 33 |   #                                in order to improve performance.
 34 |   # +:literal_prefix+ :: The prefix to output when using escaped tag delimiters (default '<%').
 35 |   # +:literal_postfix+ :: The postfix to output when using escaped tag delimiters (default '%>').
 36 |   # +:outvar+ :: Same as +:bufvar+, with lower priority.
 37 |   # +:postamble+ :: The postamble for the template, by default returns the resulting source code.
 38 |   # +:preamble+ :: The preamble for the template, by default initializes the buffer variable.
 39 |   # +:regexp+ :: The regexp to use for scanning.
 40 |   # +:src+ :: The initial value to use for the source code, an empty string by default.
 41 |   # +:trim+ :: Whether to trim leading and trailing whitespace, true by default.
 42 |   #
 43 |   # @return [Engine] a new instance of Engine
 44 |   #
 45 |   # source://erubi//lib/erubi.rb#94
 46 |   def initialize(input, properties = T.unsafe(nil)); end
 47 | 
 48 |   # The variable name used for the buffer variable.
 49 |   #
 50 |   # source://erubi//lib/erubi.rb#65
 51 |   def bufvar; end
 52 | 
 53 |   # The filename of the template, if one was given.
 54 |   #
 55 |   # source://erubi//lib/erubi.rb#62
 56 |   def filename; end
 57 | 
 58 |   # The frozen ruby source code generated from the template, which can be evaled.
 59 |   #
 60 |   # source://erubi//lib/erubi.rb#59
 61 |   def src; end
 62 | 
 63 |   private
 64 | 
 65 |   # Add ruby code to the template
 66 |   #
 67 |   # source://erubi//lib/erubi.rb#226
 68 |   def add_code(code); end
 69 | 
 70 |   # Add the given ruby expression result to the template,
 71 |   # escaping it based on the indicator given and escape flag.
 72 |   #
 73 |   # source://erubi//lib/erubi.rb#235
 74 |   def add_expression(indicator, code); end
 75 | 
 76 |   # Add the result of Ruby expression to the template
 77 |   #
 78 |   # source://erubi//lib/erubi.rb#244
 79 |   def add_expression_result(code); end
 80 | 
 81 |   # Add the escaped result of Ruby expression to the template
 82 |   #
 83 |   # source://erubi//lib/erubi.rb#249
 84 |   def add_expression_result_escaped(code); end
 85 | 
 86 |   # Add the given postamble to the src.  Can be overridden in subclasses
 87 |   # to make additional changes to src that depend on the current state.
 88 |   #
 89 |   # source://erubi//lib/erubi.rb#255
 90 |   def add_postamble(postamble); end
 91 | 
 92 |   # Add raw text to the template.  Modifies argument if argument is mutable as a memory optimization.
 93 |   # Must be called with a string, cannot be called with nil (Rails's subclass depends on it).
 94 |   #
 95 |   # source://erubi//lib/erubi.rb#213
 96 |   def add_text(text); end
 97 | 
 98 |   # Raise an exception, as the base engine class does not support handling other indicators.
 99 |   #
100 |   # @raise [ArgumentError]
101 |   #
102 |   # source://erubi//lib/erubi.rb#261
103 |   def handle(indicator, code, tailch, rspace, lspace); end
104 | 
105 |   # Make sure that any current expression has been terminated.
106 |   # The default is to terminate all expressions, but when
107 |   # the chain_appends option is used, expressions may not be
108 |   # terminated.
109 |   #
110 |   # source://erubi//lib/erubi.rb#289
111 |   def terminate_expression; end
112 | 
113 |   # Make sure the buffer variable is the target of the next append
114 |   # before yielding to the block. Mark that the buffer is the target
115 |   # of the next append after the block executes.
116 |   #
117 |   # This method should only be called if the block will result in
118 |   # code where << will append to the bufvar.
119 |   #
120 |   # source://erubi//lib/erubi.rb#271
121 |   def with_buffer; end
122 | end
123 | 
124 | # The default regular expression used for scanning.
125 | #
126 | # source://erubi//lib/erubi.rb#56
127 | Erubi::Engine::DEFAULT_REGEXP = T.let(T.unsafe(nil), Regexp)
128 | 
129 | # source://erubi//lib/erubi.rb#17
130 | Erubi::FREEZE_TEMPLATE_LITERALS = T.let(T.unsafe(nil), TrueClass)
131 | 
132 | # source://erubi//lib/erubi.rb#15
133 | Erubi::MATCH_METHOD = T.let(T.unsafe(nil), Symbol)
134 | 
135 | # source://erubi//lib/erubi.rb#8
136 | Erubi::RANGE_FIRST = T.let(T.unsafe(nil), Integer)
137 | 
138 | # source://erubi//lib/erubi.rb#9
139 | Erubi::RANGE_LAST = T.let(T.unsafe(nil), Integer)
140 | 
141 | # source://erubi//lib/erubi.rb#16
142 | Erubi::SKIP_DEFINED_FOR_INSTANCE_VARIABLE = T.let(T.unsafe(nil), TrueClass)
143 | 
144 | # source://erubi//lib/erubi.rb#4
145 | Erubi::VERSION = T.let(T.unsafe(nil), String)
146 | 
--------------------------------------------------------------------------------
/example/c/sorbet/rbi/gems/netrc@0.11.0.rbi:
--------------------------------------------------------------------------------
  1 | # typed: true
  2 | 
  3 | # DO NOT EDIT MANUALLY
  4 | # This is an autogenerated file for types exported from the `netrc` gem.
  5 | # Please instead update this file by running `bin/tapioca gem netrc`.
  6 | 
  7 | # source://netrc//lib/netrc.rb#3
  8 | class Netrc
  9 |   # @return [Netrc] a new instance of Netrc
 10 |   #
 11 |   # source://netrc//lib/netrc.rb#166
 12 |   def initialize(path, data); end
 13 | 
 14 |   # source://netrc//lib/netrc.rb#180
 15 |   def [](k); end
 16 | 
 17 |   # source://netrc//lib/netrc.rb#188
 18 |   def []=(k, info); end
 19 | 
 20 |   # source://netrc//lib/netrc.rb#200
 21 |   def delete(key); end
 22 | 
 23 |   # source://netrc//lib/netrc.rb#211
 24 |   def each(&block); end
 25 | 
 26 |   # source://netrc//lib/netrc.rb#196
 27 |   def length; end
 28 | 
 29 |   # source://netrc//lib/netrc.rb#215
 30 |   def new_item(m, l, p); end
 31 | 
 32 |   # Returns the value of attribute new_item_prefix.
 33 |   #
 34 |   # source://netrc//lib/netrc.rb#178
 35 |   def new_item_prefix; end
 36 | 
 37 |   # Sets the attribute new_item_prefix
 38 |   #
 39 |   # @param value the value to set the attribute new_item_prefix to.
 40 |   #
 41 |   # source://netrc//lib/netrc.rb#178
 42 |   def new_item_prefix=(_arg0); end
 43 | 
 44 |   # source://netrc//lib/netrc.rb#219
 45 |   def save; end
 46 | 
 47 |   # source://netrc//lib/netrc.rb#233
 48 |   def unparse; end
 49 | 
 50 |   class << self
 51 |     # source://netrc//lib/netrc.rb#42
 52 |     def check_permissions(path); end
 53 | 
 54 |     # source://netrc//lib/netrc.rb#33
 55 |     def config; end
 56 | 
 57 |     # @yield [self.config]
 58 |     #
 59 |     # source://netrc//lib/netrc.rb#37
 60 |     def configure; end
 61 | 
 62 |     # source://netrc//lib/netrc.rb#10
 63 |     def default_path; end
 64 | 
 65 |     # source://netrc//lib/netrc.rb#14
 66 |     def home_path; end
 67 | 
 68 |     # source://netrc//lib/netrc.rb#85
 69 |     def lex(lines); end
 70 | 
 71 |     # source://netrc//lib/netrc.rb#29
 72 |     def netrc_filename; end
 73 | 
 74 |     # Returns two values, a header and a list of items.
 75 |     # Each item is a tuple, containing some or all of:
 76 |     # - machine keyword (including trailing whitespace+comments)
 77 |     # - machine name
 78 |     # - login keyword (including surrounding whitespace+comments)
 79 |     # - login
 80 |     # - password keyword (including surrounding whitespace+comments)
 81 |     # - password
 82 |     # - trailing chars
 83 |     # This lets us change individual fields, then write out the file
 84 |     # with all its original formatting.
 85 |     #
 86 |     # source://netrc//lib/netrc.rb#129
 87 |     def parse(ts); end
 88 | 
 89 |     # Reads path and parses it as a .netrc file. If path doesn't
 90 |     # exist, returns an empty object. Decrypt paths ending in .gpg.
 91 |     #
 92 |     # source://netrc//lib/netrc.rb#51
 93 |     def read(path = T.unsafe(nil)); end
 94 | 
 95 |     # @return [Boolean]
 96 |     #
 97 |     # source://netrc//lib/netrc.rb#112
 98 |     def skip?(s); end
 99 |   end
100 | end
101 | 
102 | # source://netrc//lib/netrc.rb#8
103 | Netrc::CYGWIN = T.let(T.unsafe(nil), T.untyped)
104 | 
105 | # source://netrc//lib/netrc.rb#244
106 | class Netrc::Entry < ::Struct
107 |   # Returns the value of attribute login
108 |   #
109 |   # @return [Object] the current value of login
110 |   def login; end
111 | 
112 |   # Sets the attribute login
113 |   #
114 |   # @param value [Object] the value to set the attribute login to.
115 |   # @return [Object] the newly set value
116 |   def login=(_); end
117 | 
118 |   # Returns the value of attribute password
119 |   #
120 |   # @return [Object] the current value of password
121 |   def password; end
122 | 
123 |   # Sets the attribute password
124 |   #
125 |   # @param value [Object] the value to set the attribute password to.
126 |   # @return [Object] the newly set value
127 |   def password=(_); end
128 | 
129 |   def to_ary; end
130 | 
131 |   class << self
132 |     def [](*_arg0); end
133 |     def inspect; end
134 |     def keyword_init?; end
135 |     def members; end
136 |     def new(*_arg0); end
137 |   end
138 | end
139 | 
140 | # source://netrc//lib/netrc.rb#250
141 | class Netrc::Error < ::StandardError; end
142 | 
143 | # source://netrc//lib/netrc.rb#68
144 | class Netrc::TokenArray < ::Array
145 |   # source://netrc//lib/netrc.rb#76
146 |   def readto; end
147 | 
148 |   # source://netrc//lib/netrc.rb#69
149 |   def take; end
150 | end
151 | 
152 | # source://netrc//lib/netrc.rb#4
153 | Netrc::VERSION = T.let(T.unsafe(nil), String)
154 | 
155 | # see http://stackoverflow.com/questions/4871309/what-is-the-correct-way-to-detect-if-ruby-is-running-on-windows
156 | #
157 | # source://netrc//lib/netrc.rb#7
158 | Netrc::WINDOWS = T.let(T.unsafe(nil), T.untyped)
159 | 
--------------------------------------------------------------------------------
/example/c/sorbet/rbi/gems/optimist@3.1.0.rbi:
--------------------------------------------------------------------------------
1 | # typed: true
2 | 
3 | # DO NOT EDIT MANUALLY
4 | # This is an autogenerated file for types exported from the `optimist` gem.
5 | # Please instead update this file by running `bin/tapioca gem optimist`.
6 | 
7 | # THIS IS AN EMPTY RBI FILE.
8 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem
9 | 
--------------------------------------------------------------------------------
/example/c/sorbet/rbi/gems/parallel@1.23.0.rbi:
--------------------------------------------------------------------------------
  1 | # typed: true
  2 | 
  3 | # DO NOT EDIT MANUALLY
  4 | # This is an autogenerated file for types exported from the `parallel` gem.
  5 | # Please instead update this file by running `bin/tapioca gem parallel`.
  6 | 
  7 | # source://parallel//lib/parallel/version.rb#2
  8 | module Parallel
  9 |   class << self
 10 |     # @return [Boolean]
 11 |     #
 12 |     # source://parallel//lib/parallel.rb#243
 13 |     def all?(*args, &block); end
 14 | 
 15 |     # @return [Boolean]
 16 |     #
 17 |     # source://parallel//lib/parallel.rb#238
 18 |     def any?(*args, &block); end
 19 | 
 20 |     # source://parallel//lib/parallel.rb#234
 21 |     def each(array, options = T.unsafe(nil), &block); end
 22 | 
 23 |     # source://parallel//lib/parallel.rb#248
 24 |     def each_with_index(array, options = T.unsafe(nil), &block); end
 25 | 
 26 |     # source://parallel//lib/parallel.rb#307
 27 |     def filter_map(*args, &block); end
 28 | 
 29 |     # source://parallel//lib/parallel.rb#303
 30 |     def flat_map(*args, &block); end
 31 | 
 32 |     # source://parallel//lib/parallel.rb#228
 33 |     def in_processes(options = T.unsafe(nil), &block); end
 34 | 
 35 |     # source://parallel//lib/parallel.rb#212
 36 |     def in_threads(options = T.unsafe(nil)); end
 37 | 
 38 |     # source://parallel//lib/parallel.rb#252
 39 |     def map(source, options = T.unsafe(nil), &block); end
 40 | 
 41 |     # source://parallel//lib/parallel.rb#299
 42 |     def map_with_index(array, options = T.unsafe(nil), &block); end
 43 | 
 44 |     # Number of physical processor cores on the current system.
 45 |     #
 46 |     # source://parallel//lib/parallel.rb#312
 47 |     def physical_processor_count; end
 48 | 
 49 |     # Number of processors seen by the OS, used for process scheduling
 50 |     #
 51 |     # source://parallel//lib/parallel.rb#345
 52 |     def processor_count; end
 53 | 
 54 |     # source://parallel//lib/parallel.rb#350
 55 |     def worker_number; end
 56 | 
 57 |     # TODO: this does not work when doing threads in forks, so should remove and yield the number instead if needed
 58 |     #
 59 |     # source://parallel//lib/parallel.rb#355
 60 |     def worker_number=(worker_num); end
 61 | 
 62 |     private
 63 | 
 64 |     # source://parallel//lib/parallel.rb#361
 65 |     def add_progress_bar!(job_factory, options); end
 66 | 
 67 |     # source://parallel//lib/parallel.rb#624
 68 |     def call_with_index(item, index, options, &block); end
 69 | 
 70 |     # source://parallel//lib/parallel.rb#556
 71 |     def create_workers(job_factory, options, &block); end
 72 | 
 73 |     # options is either a Integer or a Hash with :count
 74 |     #
 75 |     # source://parallel//lib/parallel.rb#614
 76 |     def extract_count_from_options(options); end
 77 | 
 78 |     # source://parallel//lib/parallel.rb#642
 79 |     def instrument_finish(item, index, result, options); end
 80 | 
 81 |     # source://parallel//lib/parallel.rb#647
 82 |     def instrument_start(item, index, options); end
 83 | 
 84 |     # source://parallel//lib/parallel.rb#590
 85 |     def process_incoming_jobs(read, write, job_factory, options, &block); end
 86 | 
 87 |     # source://parallel//lib/parallel.rb#544
 88 |     def replace_worker(job_factory, workers, index, options, blk); end
 89 | 
 90 |     # source://parallel//lib/parallel.rb#635
 91 |     def with_instrumentation(item, index, options); end
 92 | 
 93 |     # source://parallel//lib/parallel.rb#386
 94 |     def work_direct(job_factory, options, &block); end
 95 | 
 96 |     # source://parallel//lib/parallel.rb#496
 97 |     def work_in_processes(job_factory, options, &blk); end
 98 | 
 99 |     # source://parallel//lib/parallel.rb#430
100 |     def work_in_ractors(job_factory, options); end
101 | 
102 |     # source://parallel//lib/parallel.rb#405
103 |     def work_in_threads(job_factory, options, &block); end
104 | 
105 |     # source://parallel//lib/parallel.rb#564
106 |     def worker(job_factory, options, &block); end
107 |   end
108 | end
109 | 
110 | # source://parallel//lib/parallel.rb#11
111 | class Parallel::Break < ::StandardError
112 |   # @return [Break] a new instance of Break
113 |   #
114 |   # source://parallel//lib/parallel.rb#14
115 |   def initialize(value = T.unsafe(nil)); end
116 | 
117 |   # Returns the value of attribute value.
118 |   #
119 |   # source://parallel//lib/parallel.rb#12
120 |   def value; end
121 | end
122 | 
123 | # source://parallel//lib/parallel.rb#8
124 | class Parallel::DeadWorker < ::StandardError; end
125 | 
126 | # source://parallel//lib/parallel.rb#32
127 | class Parallel::ExceptionWrapper
128 |   # @return [ExceptionWrapper] a new instance of ExceptionWrapper
129 |   #
130 |   # source://parallel//lib/parallel.rb#35
131 |   def initialize(exception); end
132 | 
133 |   # Returns the value of attribute exception.
134 |   #
135 |   # source://parallel//lib/parallel.rb#33
136 |   def exception; end
137 | end
138 | 
139 | # source://parallel//lib/parallel.rb#98
140 | class Parallel::JobFactory
141 |   # @return [JobFactory] a new instance of JobFactory
142 |   #
143 |   # source://parallel//lib/parallel.rb#99
144 |   def initialize(source, mutex); end
145 | 
146 |   # source://parallel//lib/parallel.rb#107
147 |   def next; end
148 | 
149 |   # generate item that is sent to workers
150 |   # just index is faster + less likely to blow up with unserializable errors
151 |   #
152 |   # source://parallel//lib/parallel.rb#136
153 |   def pack(item, index); end
154 | 
155 |   # source://parallel//lib/parallel.rb#126
156 |   def size; end
157 | 
158 |   # unpack item that is sent to workers
159 |   #
160 |   # source://parallel//lib/parallel.rb#141
161 |   def unpack(data); end
162 | 
163 |   private
164 | 
165 |   # @return [Boolean]
166 |   #
167 |   # source://parallel//lib/parallel.rb#147
168 |   def producer?; end
169 | 
170 |   # source://parallel//lib/parallel.rb#151
171 |   def queue_wrapper(array); end
172 | end
173 | 
174 | # source://parallel//lib/parallel.rb#20
175 | class Parallel::Kill < ::Parallel::Break; end
176 | 
177 | # source://parallel//lib/parallel.rb#6
178 | Parallel::Stop = T.let(T.unsafe(nil), Object)
179 | 
180 | # source://parallel//lib/parallel.rb#23
181 | class Parallel::UndumpableException < ::StandardError
182 |   # @return [UndumpableException] a new instance of UndumpableException
183 |   #
184 |   # source://parallel//lib/parallel.rb#26
185 |   def initialize(original); end
186 | 
187 |   # Returns the value of attribute backtrace.
188 |   #
189 |   # source://parallel//lib/parallel.rb#24
190 |   def backtrace; end
191 | end
192 | 
193 | # source://parallel//lib/parallel.rb#156
194 | class Parallel::UserInterruptHandler
195 |   class << self
196 |     # source://parallel//lib/parallel.rb#181
197 |     def kill(thing); end
198 | 
199 |     # kill all these pids or threads if user presses Ctrl+c
200 |     #
201 |     # source://parallel//lib/parallel.rb#161
202 |     def kill_on_ctrl_c(pids, options); end
203 | 
204 |     private
205 | 
206 |     # source://parallel//lib/parallel.rb#205
207 |     def restore_interrupt(old, signal); end
208 | 
209 |     # source://parallel//lib/parallel.rb#190
210 |     def trap_interrupt(signal); end
211 |   end
212 | end
213 | 
214 | # source://parallel//lib/parallel.rb#157
215 | Parallel::UserInterruptHandler::INTERRUPT_SIGNAL = T.let(T.unsafe(nil), Symbol)
216 | 
217 | # source://parallel//lib/parallel/version.rb#3
218 | Parallel::VERSION = T.let(T.unsafe(nil), String)
219 | 
220 | # source://parallel//lib/parallel/version.rb#3
221 | Parallel::Version = T.let(T.unsafe(nil), String)
222 | 
223 | # source://parallel//lib/parallel.rb#51
224 | class Parallel::Worker
225 |   # @return [Worker] a new instance of Worker
226 |   #
227 |   # source://parallel//lib/parallel.rb#55
228 |   def initialize(read, write, pid); end
229 | 
230 |   # might be passed to started_processes and simultaneously closed by another thread
231 |   # when running in isolation mode, so we have to check if it is closed before closing
232 |   #
233 |   # source://parallel//lib/parallel.rb#68
234 |   def close_pipes; end
235 | 
236 |   # Returns the value of attribute pid.
237 |   #
238 |   # source://parallel//lib/parallel.rb#52
239 |   def pid; end
240 | 
241 |   # Returns the value of attribute read.
242 |   #
243 |   # source://parallel//lib/parallel.rb#52
244 |   def read; end
245 | 
246 |   # source://parallel//lib/parallel.rb#61
247 |   def stop; end
248 | 
249 |   # Returns the value of attribute thread.
250 |   #
251 |   # source://parallel//lib/parallel.rb#53
252 |   def thread; end
253 | 
254 |   # Sets the attribute thread
255 |   #
256 |   # @param value the value to set the attribute thread to.
257 |   #
258 |   # source://parallel//lib/parallel.rb#53
259 |   def thread=(_arg0); end
260 | 
261 |   # source://parallel//lib/parallel.rb#73
262 |   def work(data); end
263 | 
264 |   # Returns the value of attribute write.
265 |   #
266 |   # source://parallel//lib/parallel.rb#52
267 |   def write; end
268 | 
269 |   private
270 | 
271 |   # source://parallel//lib/parallel.rb#91
272 |   def wait; end
273 | end
274 | 
--------------------------------------------------------------------------------
/example/c/sorbet/rbi/gems/patience_diff@1.2.0.rbi:
--------------------------------------------------------------------------------
  1 | # typed: true
  2 | 
  3 | # DO NOT EDIT MANUALLY
  4 | # This is an autogenerated file for types exported from the `patience_diff` gem.
  5 | # Please instead update this file by running `bin/tapioca gem patience_diff`.
  6 | 
  7 | # source://patience_diff//lib/patience_diff/formatting_context.rb#3
  8 | module PatienceDiff; end
  9 | 
 10 | # source://patience_diff//lib/patience_diff/differ.rb#6
 11 | class PatienceDiff::Differ
 12 |   # Options:
 13 |   #   * :all_context: Output the entirety of each file. This overrides the sequence matcher's context setting.
 14 |   #   * :line_ending: Delimiter to use when joining diff output. Defaults to $RS.
 15 |   #   * :ignore_whitespace: Before comparing lines, strip trailing whitespace, and treat leading whitespace
 16 |   #     as either present or not. Does not affect output.
 17 |   # Any additional options (e.g. :context) are passed on to the sequence matcher.
 18 |   #
 19 |   # @return [Differ] a new instance of Differ
 20 |   #
 21 |   # source://patience_diff//lib/patience_diff/differ.rb#16
 22 |   def initialize(opts = T.unsafe(nil)); end
 23 | 
 24 |   # Returns the value of attribute all_context.
 25 |   #
 26 |   # source://patience_diff//lib/patience_diff/differ.rb#8
 27 |   def all_context; end
 28 | 
 29 |   # Sets the attribute all_context
 30 |   #
 31 |   # @param value the value to set the attribute all_context to.
 32 |   #
 33 |   # source://patience_diff//lib/patience_diff/differ.rb#8
 34 |   def all_context=(_arg0); end
 35 | 
 36 |   # Generates a unified diff from the contents of the files at the paths specified.
 37 |   #
 38 |   # source://patience_diff//lib/patience_diff/differ.rb#24
 39 |   def diff_files(left_file, right_file, formatter = T.unsafe(nil)); end
 40 | 
 41 |   # Generate a unified diff of the data specified. The left and right values should be strings, or any other indexable, sortable data.
 42 |   # File names and timestamps do not affect the diff algorithm, but are used in the header text.
 43 |   #
 44 |   # source://patience_diff//lib/patience_diff/differ.rb#36
 45 |   def diff_sequences(left, right, left_name = T.unsafe(nil), right_name = T.unsafe(nil), left_timestamp = T.unsafe(nil), right_timestamp = T.unsafe(nil), formatter = T.unsafe(nil)); end
 46 | 
 47 |   # Returns the value of attribute ignore_whitespace.
 48 |   #
 49 |   # source://patience_diff//lib/patience_diff/differ.rb#8
 50 |   def ignore_whitespace; end
 51 | 
 52 |   # Sets the attribute ignore_whitespace
 53 |   #
 54 |   # @param value the value to set the attribute ignore_whitespace to.
 55 |   #
 56 |   # source://patience_diff//lib/patience_diff/differ.rb#8
 57 |   def ignore_whitespace=(_arg0); end
 58 | 
 59 |   # Returns the value of attribute line_ending.
 60 |   #
 61 |   # source://patience_diff//lib/patience_diff/differ.rb#8
 62 |   def line_ending; end
 63 | 
 64 |   # Sets the attribute line_ending
 65 |   #
 66 |   # @param value the value to set the attribute line_ending to.
 67 |   #
 68 |   # source://patience_diff//lib/patience_diff/differ.rb#8
 69 |   def line_ending=(_arg0); end
 70 | 
 71 |   # Returns the value of attribute matcher.
 72 |   #
 73 |   # source://patience_diff//lib/patience_diff/differ.rb#7
 74 |   def matcher; end
 75 | end
 76 | 
 77 | # Formats a plaintext unified diff.
 78 | #
 79 | # source://patience_diff//lib/patience_diff/formatter.rb#5
 80 | class PatienceDiff::Formatter
 81 |   # @return [Formatter] a new instance of Formatter
 82 |   #
 83 |   # source://patience_diff//lib/patience_diff/formatter.rb#9
 84 |   def initialize(differ, title = T.unsafe(nil)); end
 85 | 
 86 |   # @yield [context]
 87 |   #
 88 |   # source://patience_diff//lib/patience_diff/formatter.rb#15
 89 |   def format; end
 90 | 
 91 |   # Returns the value of attribute left_name.
 92 |   #
 93 |   # source://patience_diff//lib/patience_diff/formatter.rb#7
 94 |   def left_name; end
 95 | 
 96 |   # Sets the attribute left_name
 97 |   #
 98 |   # @param value the value to set the attribute left_name to.
 99 |   #
100 |   # source://patience_diff//lib/patience_diff/formatter.rb#7
101 |   def left_name=(_arg0); end
102 | 
103 |   # Returns the value of attribute left_timestamp.
104 |   #
105 |   # source://patience_diff//lib/patience_diff/formatter.rb#7
106 |   def left_timestamp; end
107 | 
108 |   # Sets the attribute left_timestamp
109 |   #
110 |   # @param value the value to set the attribute left_timestamp to.
111 |   #
112 |   # source://patience_diff//lib/patience_diff/formatter.rb#7
113 |   def left_timestamp=(_arg0); end
114 | 
115 |   # Returns the value of attribute names.
116 |   #
117 |   # source://patience_diff//lib/patience_diff/formatter.rb#6
118 |   def names; end
119 | 
120 |   # source://patience_diff//lib/patience_diff/formatter.rb#21
121 |   def render_header(left_name = T.unsafe(nil), right_name = T.unsafe(nil), left_timestamp = T.unsafe(nil), right_timestamp = T.unsafe(nil)); end
122 | 
123 |   # source://patience_diff//lib/patience_diff/formatter.rb#42
124 |   def render_hunk(a, b, opcodes, last_line_shown); end
125 | 
126 |   # source://patience_diff//lib/patience_diff/formatter.rb#33
127 |   def render_hunk_marker(opcodes); end
128 | 
129 |   # Returns the value of attribute right_name.
130 |   #
131 |   # source://patience_diff//lib/patience_diff/formatter.rb#7
132 |   def right_name; end
133 | 
134 |   # Sets the attribute right_name
135 |   #
136 |   # @param value the value to set the attribute right_name to.
137 |   #
138 |   # source://patience_diff//lib/patience_diff/formatter.rb#7
139 |   def right_name=(_arg0); end
140 | 
141 |   # Returns the value of attribute right_timestamp.
142 |   #
143 |   # source://patience_diff//lib/patience_diff/formatter.rb#7
144 |   def right_timestamp; end
145 | 
146 |   # Sets the attribute right_timestamp
147 |   #
148 |   # @param value the value to set the attribute right_timestamp to.
149 |   #
150 |   # source://patience_diff//lib/patience_diff/formatter.rb#7
151 |   def right_timestamp=(_arg0); end
152 | 
153 |   # Returns the value of attribute title.
154 |   #
155 |   # source://patience_diff//lib/patience_diff/formatter.rb#7
156 |   def title; end
157 | 
158 |   # Sets the attribute title
159 |   #
160 |   # @param value the value to set the attribute title to.
161 |   #
162 |   # source://patience_diff//lib/patience_diff/formatter.rb#7
163 |   def title=(_arg0); end
164 | 
165 |   private
166 | 
167 |   # source://patience_diff//lib/patience_diff/formatter.rb#58
168 |   def left_header_line(name, timestamp); end
169 | 
170 |   # source://patience_diff//lib/patience_diff/formatter.rb#62
171 |   def right_header_line(name, timestamp); end
172 | end
173 | 
174 | # Delegate object yielded by the #format method.
175 | #
176 | # source://patience_diff//lib/patience_diff/formatting_context.rb#5
177 | class PatienceDiff::FormattingContext
178 |   # @return [FormattingContext] a new instance of FormattingContext
179 |   #
180 |   # source://patience_diff//lib/patience_diff/formatting_context.rb#6
181 |   def initialize(differ, formatter); end
182 | 
183 |   # source://patience_diff//lib/patience_diff/formatting_context.rb#12
184 |   def files(left_file, right_file); end
185 | 
186 |   # source://patience_diff//lib/patience_diff/formatting_context.rb#24
187 |   def format; end
188 | 
189 |   # source://patience_diff//lib/patience_diff/formatting_context.rb#32
190 |   def names; end
191 | 
192 |   # source://patience_diff//lib/patience_diff/formatting_context.rb#20
193 |   def orphan(sequence, name = T.unsafe(nil), timestamp = T.unsafe(nil)); end
194 | 
195 |   # source://patience_diff//lib/patience_diff/formatting_context.rb#16
196 |   def sequences(left, right, left_name = T.unsafe(nil), right_name = T.unsafe(nil), left_timestamp = T.unsafe(nil), right_timestamp = T.unsafe(nil)); end
197 | 
198 |   # source://patience_diff//lib/patience_diff/formatting_context.rb#28
199 |   def title; end
200 | end
201 | 
202 | # Matches indexed data (generally text) using the Patience diff algorithm.
203 | #
204 | # source://patience_diff//lib/patience_diff/sequence_matcher.rb#3
205 | class PatienceDiff::SequenceMatcher
206 |   # Options:
207 |   #   * :context: number of lines of context to use when grouping
208 |   #
209 |   # @return [SequenceMatcher] a new instance of SequenceMatcher
210 |   #
211 |   # source://patience_diff//lib/patience_diff/sequence_matcher.rb#10
212 |   def initialize(opts = T.unsafe(nil)); end
213 | 
214 |   # Returns the value of attribute context.
215 |   #
216 |   # source://patience_diff//lib/patience_diff/sequence_matcher.rb#4
217 |   def context; end
218 | 
219 |   # Sets the attribute context
220 |   #
221 |   # @param value the value to set the attribute context to.
222 |   #
223 |   # source://patience_diff//lib/patience_diff/sequence_matcher.rb#4
224 |   def context=(_arg0); end
225 | 
226 |   # Generate a diff of a and b, and return an array of opcodes describing that diff.
227 |   # Each opcode represents a range in a and b that is either equal, only in a,
228 |   # or only in b. Opcodes are 5-tuples, in the format:
229 |   #   0: code
230 |   #      A symbol indicating the diff operation. Can be :equal, :delete, or :insert.
231 |   #   1: a_start
232 |   #      Index in a where the range begins
233 |   #   2: a_end
234 |   #      Index in a where the range ends.
235 |   #   3: b_start
236 |   #      Index in b where the range begins
237 |   #   4: b_end
238 |   #      Index in b where the range ends.
239 |   #
240 |   # For :equal, (a_end - a_start) == (b_end - b_start).
241 |   # For :delete, a_start == a_end.
242 |   # For :insert, b_start == b_end.
243 |   #
244 |   # source://patience_diff//lib/patience_diff/sequence_matcher.rb#80
245 |   def diff_opcodes(a, b); end
246 | 
247 |   # Generate a diff of a and b using #diff_opcodes, and split the opcode into groups
248 |   # whenever an :equal range is encountered that is longer than @context * 2.
249 |   # Returns an array of arrays of 5-tuples as described for #diff_opcodes.
250 |   #
251 |   # source://patience_diff//lib/patience_diff/sequence_matcher.rb#17
252 |   def grouped_opcodes(a, b); end
253 | 
254 |   private
255 | 
256 |   # source://patience_diff//lib/patience_diff/sequence_matcher.rb#242
257 |   def bisect(piles, target); end
258 | 
259 |   # source://patience_diff//lib/patience_diff/sequence_matcher.rb#158
260 |   def collapse_matches(matches); end
261 | 
262 |   # source://patience_diff//lib/patience_diff/sequence_matcher.rb#177
263 |   def longest_unique_subsequence(a, b); end
264 | 
265 |   # source://patience_diff//lib/patience_diff/sequence_matcher.rb#103
266 |   def match(a, b); end
267 | 
268 |   # source://patience_diff//lib/patience_diff/sequence_matcher.rb#212
269 |   def patience_sort(deck); end
270 | 
271 |   # source://patience_diff//lib/patience_diff/sequence_matcher.rb#111
272 |   def recursively_match(a, b, a_lo, b_lo, a_hi, b_hi); end
273 | end
274 | 
275 | # source://patience_diff//lib/patience_diff/sequence_matcher.rb#6
276 | class PatienceDiff::SequenceMatcher::Card < ::Struct
277 |   # Returns the value of attribute index
278 |   #
279 |   # @return [Object] the current value of index
280 |   def index; end
281 | 
282 |   # Sets the attribute index
283 |   #
284 |   # @param value [Object] the value to set the attribute index to.
285 |   # @return [Object] the newly set value
286 |   def index=(_); end
287 | 
288 |   # Returns the value of attribute previous
289 |   #
290 |   # @return [Object] the current value of previous
291 |   def previous; end
292 | 
293 |   # Sets the attribute previous
294 |   #
295 |   # @param value [Object] the value to set the attribute previous to.
296 |   # @return [Object] the newly set value
297 |   def previous=(_); end
298 | 
299 |   # Returns the value of attribute value
300 |   #
301 |   # @return [Object] the current value of value
302 |   def value; end
303 | 
304 |   # Sets the attribute value
305 |   #
306 |   # @param value [Object] the value to set the attribute value to.
307 |   # @return [Object] the newly set value
308 |   def value=(_); end
309 | 
310 |   class << self
311 |     def [](*_arg0); end
312 |     def inspect; end
313 |     def keyword_init?; end
314 |     def members; end
315 |     def new(*_arg0); end
316 |   end
317 | end
318 | 
319 | # source://patience_diff//lib/patience_diff.rb#11
320 | PatienceDiff::TEMPLATE_PATH = T.let(T.unsafe(nil), Pathname)
321 | 
322 | # source://patience_diff//lib/patience_diff/usage_error.rb#2
323 | class PatienceDiff::UsageError < ::StandardError; end
324 | 
325 | # source://patience_diff//lib/patience_diff.rb#10
326 | PatienceDiff::VERSION = T.let(T.unsafe(nil), String)
327 | 
--------------------------------------------------------------------------------
/example/c/sorbet/tapioca/config.yml:
--------------------------------------------------------------------------------
 1 | gem:
 2 |   # Add your `gem` command parameters here:
 3 |   #
 4 |   # exclude:
 5 |   # - gem_name
 6 |   # doc: true
 7 |   # workers: 5
 8 | dsl:
 9 |   # Add your `dsl` command parameters here:
10 |   #
11 |   # exclude:
12 |   # - SomeGeneratorName
13 |   # workers: 5
14 | 
--------------------------------------------------------------------------------
/example/c/sorbet/tapioca/require.rb:
--------------------------------------------------------------------------------
1 | # typed: true
2 | # frozen_string_literal: true
3 | 
4 | # Add your extra requires here (`bin/tapioca require` can be used to bootstrap this list)
5 | 
--------------------------------------------------------------------------------
/example/c/spec/math_spec.rb:
--------------------------------------------------------------------------------
 1 | # typed: strict
 2 | 
 3 | class MathSpec < TLDR
 4 |   extend T::Sig
 5 |   print_specs!
 6 | 
 7 |   sig { void }
 8 |   def test_it_is_math
 9 |     assert_equal 1 + 1, 2
10 |   end
11 | end
12 | 
--------------------------------------------------------------------------------
/example/c/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require "sorbet-runtime"
2 | 
3 | class TLDR
4 |   def self.print_specs!
5 |     puts "👓"
6 |   end
7 | end
8 | 
--------------------------------------------------------------------------------
/example/d/.tldr.yml:
--------------------------------------------------------------------------------
 1 | seed: 42
 2 | verbose: true
 3 | reporter: "TLDR::Reporters::Default"
 4 | helper_paths: ["test_helper.rb"]
 5 | load_paths: ["app", "lib"]
 6 | parallel: true
 7 | names: ["/test_*/", "test_it"]
 8 | fail_fast: true
 9 | prepend_paths: ["a.rb:3"]
10 | paths: ["a.rb:3", "b.rb"]
11 | exclude_paths: ["c.rb:4"]
12 | exclude_names: ["test_b_1"]
13 | 
--------------------------------------------------------------------------------
/example/d/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 | 
3 | gem "tldr", path: "../.."
4 | 
--------------------------------------------------------------------------------
/example/d/Gemfile.lock:
--------------------------------------------------------------------------------
 1 | PATH
 2 |   remote: ../..
 3 |   specs:
 4 |     tldr (1.1.1)
 5 |       concurrent-ruby (~> 1.2)
 6 |       irb (~> 1.10)
 7 |       super_diff (~> 0.10)
 8 | 
 9 | GEM
10 |   remote: https://rubygems.org/
11 |   specs:
12 |     attr_extras (7.1.0)
13 |     cgi (0.5.0)
14 |     concurrent-ruby (1.3.5)
15 |     date (3.4.1)
16 |     diff-lcs (1.6.2)
17 |     erb (4.0.4)
18 |       cgi (>= 0.3.3)
19 |     io-console (0.8.0)
20 |     irb (1.15.2)
21 |       pp (>= 0.6.0)
22 |       rdoc (>= 4.0.0)
23 |       reline (>= 0.4.2)
24 |     optimist (3.2.1)
25 |     patience_diff (1.2.0)
26 |       optimist (~> 3.0)
27 |     pp (0.6.2)
28 |       prettyprint
29 |     prettyprint (0.2.0)
30 |     psych (5.2.6)
31 |       date
32 |       stringio
33 |     rdoc (6.14.2)
34 |       erb
35 |       psych (>= 4.0.0)
36 |     reline (0.6.1)
37 |       io-console (~> 0.5)
38 |     stringio (3.1.7)
39 |     super_diff (0.16.0)
40 |       attr_extras (>= 6.2.4)
41 |       diff-lcs
42 |       patience_diff
43 | 
44 | PLATFORMS
45 |   arm64-darwin-22
46 |   arm64-darwin-23
47 |   arm64-darwin-24
48 | 
49 | DEPENDENCIES
50 |   tldr!
51 | 
52 | BUNDLED WITH
53 |    2.4.17
54 | 
--------------------------------------------------------------------------------
/example/d/b.rb:
--------------------------------------------------------------------------------
 1 | class BTest < TLDR
 2 |   def test_b_1
 3 |     # Should be excluded
 4 |   end
 5 | 
 6 |   def test_b_2
 7 |     fail "wups"
 8 |   end
 9 | end
10 | 
--------------------------------------------------------------------------------
/exe/tldr:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | 
3 | $LOAD_PATH.unshift("#{__dir__}/../lib")
4 | 
5 | require "tldr"
6 | 
7 | TLDR::Run.cli(ARGV)
8 | 
--------------------------------------------------------------------------------
/exe/tldt:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | 
3 | $LOAD_PATH.unshift("#{__dir__}/../lib")
4 | 
5 | require "tldr"
6 | 
7 | TLDR::Run.cli(ARGV)
8 | 
--------------------------------------------------------------------------------
/lib/tldr.rb:
--------------------------------------------------------------------------------
 1 | require "concurrent-ruby"
 2 | 
 3 | require_relative "tldr/argv_parser"
 4 | require_relative "tldr/argv_reconstructor"
 5 | require_relative "tldr/assertions"
 6 | require_relative "tldr/backtrace_filter"
 7 | require_relative "tldr/class_util"
 8 | require_relative "tldr/error"
 9 | require_relative "tldr/executor"
10 | require_relative "tldr/hooks"
11 | require_relative "tldr/parallel_controls"
12 | require_relative "tldr/path_util"
13 | require_relative "tldr/planner"
14 | require_relative "tldr/minitest_compatibility"
15 | require_relative "tldr/reporters"
16 | require_relative "tldr/ruby_util"
17 | require_relative "tldr/runner"
18 | require_relative "tldr/skippable"
19 | require_relative "tldr/sorbet_compatibility"
20 | require_relative "tldr/strategizer"
21 | require_relative "tldr/value"
22 | require_relative "tldr/version"
23 | require_relative "tldr/watcher"
24 | require_relative "tldr/yaml_parser"
25 | 
26 | class TLDR
27 |   include Assertions
28 |   include Skippable
29 |   include Hooks
30 | 
31 |   module Run
32 |     def self.cli argv
33 |       config = ArgvParser.new.parse(argv)
34 |       tests(config)
35 |     end
36 | 
37 |     def self.tests config = Config.new
38 |       if config.watch
39 |         Watcher.new.watch(config)
40 |       else
41 |         PathUtil.chdir_maybe(config.base_path) do
42 |           Runner.new.run(config, Planner.new.plan(config))
43 |         end
44 |       end
45 |     end
46 | 
47 |     @@at_exit_registered = false
48 |     def self.at_exit! config = Config.new
49 |       # Ignore at_exit when running tldr CLI, since that will run any tests
50 |       return if $PROGRAM_NAME.end_with?("tldr")
51 |       # Also ignore if we're running from within our rake task
52 |       return if caller.any? { |line| line.include?("lib/tldr/rake.rb") }
53 |       # Ignore at_exit when we've already registered an at_exit hook
54 |       return if @@at_exit_registered
55 | 
56 |       Kernel.at_exit do
57 |         Run.tests(config)
58 |       end
59 | 
60 |       @@at_exit_registered = true
61 |     end
62 |   end
63 | end
64 | 
--------------------------------------------------------------------------------
/lib/tldr/argv_parser.rb:
--------------------------------------------------------------------------------
  1 | require "optparse"
  2 | 
  3 | class TLDR
  4 |   class ArgvParser
  5 |     PATTERN_FRIENDLY_SPLITTER = /,(?=(?:[^\/]*\/[^\/]*\/)*[^\/]*$)/
  6 | 
  7 |     def parse args, options = {cli_defaults: true}
  8 |       og_args = args.dup
  9 |       OptionParser.new do |opts|
 10 |         opts.banner = "Usage: tldr [options] some_tests/**/*.rb some/path.rb:13 ..."
 11 | 
 12 |         opts.on "-t", "--[no-]timeout [TIMEOUT]", "Timeout (in seconds) before timer aborts the run (Default: #{Config::DEFAULT_TIMEOUT})" do |timeout|
 13 |           options[:timeout] = if timeout == false
 14 |             # --no-timeout
 15 |             -1
 16 |           elsif timeout.nil?
 17 |             # --timeout
 18 |             Config::DEFAULT_TIMEOUT
 19 |           else
 20 |             # --timeout 42.3
 21 |             handle_unparsable_optional_value(og_args, "timeout", timeout) do
 22 |               Float(timeout)
 23 |             end
 24 |           end
 25 |         end
 26 | 
 27 |         opts.on CONFLAGS[:watch], "Run your tests continuously on file save (requires 'fswatch' to be installed)" do
 28 |           options[:watch] = true
 29 |         end
 30 | 
 31 |         opts.on CONFLAGS[:fail_fast], "Stop running tests as soon as one fails" do |fail_fast|
 32 |           options[:fail_fast] = fail_fast
 33 |         end
 34 | 
 35 |         opts.on CONFLAGS[:parallel], "Parallelize tests (Default: true)" do |parallel|
 36 |           options[:parallel] = parallel
 37 |         end
 38 | 
 39 |         opts.on "-s", "#{CONFLAGS[:seed]} SEED", Integer, "Random seed for test order (setting --seed disables parallelization by default)" do |seed|
 40 |           options[:seed] = seed
 41 |         end
 42 | 
 43 |         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|
 44 |           options[:names] ||= []
 45 |           options[:names] += name.split(PATTERN_FRIENDLY_SPLITTER)
 46 |         end
 47 | 
 48 |         opts.on "#{CONFLAGS[:exclude_names]} PATTERN", "One or more names or /patterns/ NOT to run" do |exclude_name|
 49 |           options[:exclude_names] ||= []
 50 |           options[:exclude_names] += exclude_name.split(PATTERN_FRIENDLY_SPLITTER)
 51 |         end
 52 | 
 53 |         opts.on "#{CONFLAGS[:exclude_paths]} PATH", Array, "One or more paths NOT to run (like: foo.rb, \"test/bar/**\", baz.rb:3)" do |path|
 54 |           options[:exclude_paths] ||= []
 55 |           options[:exclude_paths] += path
 56 |         end
 57 | 
 58 |         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|
 59 |           options[:helper_paths] ||= []
 60 |           options[:helper_paths] += path
 61 |         end
 62 | 
 63 |         opts.on CONFLAGS[:no_helper], "Don't require any test helpers" do
 64 |           options[:no_helper] = true
 65 |         end
 66 | 
 67 |         opts.on "#{CONFLAGS[:prepend_paths]} PATH", Array, "Prepend one or more paths to run before the rest (Default: most recently modified test)" do |prepend|
 68 |           options[:prepend_paths] ||= []
 69 |           options[:prepend_paths] += prepend
 70 |         end
 71 | 
 72 |         opts.on CONFLAGS[:no_prepend], "Don't prepend any tests before the rest of the suite" do
 73 |           options[:no_prepend] = true
 74 |         end
 75 | 
 76 |         opts.on "-l", "#{CONFLAGS[:load_paths]} PATH", Array, "Add one or more paths to the $LOAD_PATH (Default: [\"lib\", \"test\"])" do |load_path|
 77 |           options[:load_paths] ||= []
 78 |           options[:load_paths] += load_path
 79 |         end
 80 | 
 81 |         opts.on "#{CONFLAGS[:base_path]} PATH", String, "Change the working directory for all relative paths (Default: current working directory)" do |path|
 82 |           options[:base_path] = path
 83 |         end
 84 | 
 85 |         opts.on "-c", "#{CONFLAGS[:config_path]} PATH", String, "The YAML configuration file to load (Default: '.tldr.yml')" do |config_path|
 86 |           options[:config_path] = config_path
 87 |         end
 88 | 
 89 |         opts.on "-r", "#{CONFLAGS[:reporter]} REPORTER", String, "Set a custom reporter class (Default: \"TLDR::Reporters::Default\")" do |reporter|
 90 |           options[:reporter] = reporter
 91 |         end
 92 | 
 93 |         opts.on CONFLAGS[:emoji], "Enable emoji output for the default reporter (Default: false)" do |emoji|
 94 |           options[:emoji] = emoji
 95 |         end
 96 | 
 97 |         opts.on CONFLAGS[:warnings], "Print Ruby warnings (Default: true)" do |warnings|
 98 |           options[:warnings] = warnings
 99 |         end
100 | 
101 |         opts.on "-v", CONFLAGS[:verbose], "Print stack traces for errors" do |verbose|
102 |           options[:verbose] = verbose
103 |         end
104 | 
105 |         opts.on CONFLAGS[:yes_i_know], "Suppress TLDR report when suite runs beyond any configured --timeout" do
106 |           options[:yes_i_know] = true
107 |         end
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 | 
117 |         opts.on CONFLAGS[:print_interrupted_test_backtraces], "Print stack traces of tests interrupted after a timeout" do |print_interrupted_test_backtraces|
118 |           options[:print_interrupted_test_backtraces] = print_interrupted_test_backtraces
119 |         end
120 | 
121 |         unless ARGV.include?("--help") || ARGV.include?("--h")
122 |           opts.on CONFLAGS[:i_am_being_watched], "[INTERNAL] Signals to tldr it is being invoked under --watch mode" do
123 |             options[:i_am_being_watched] = true
124 |           end
125 | 
126 |           opts.on "--comment COMMENT", String, "[INTERNAL] No-op; used for multi-line execution instructions" do
127 |             # See "--comment" in lib/tldr/reporters/default.rb for an example of how this is used internally
128 |           end
129 |         end
130 |       end.parse!(args)
131 | 
132 |       options[:paths] = args if args.any?
133 |       options[:config_path] = case options[:config_path]
134 |       when nil then Config::DEFAULT_YAML_PATH
135 |       when false then nil
136 |       else options[:config_path]
137 |       end
138 | 
139 |       Config.new(**options)
140 |     end
141 | 
142 |     private
143 | 
144 |     def handle_unparsable_optional_value(og_args, option_name, value, &blk)
145 |       yield
146 |     rescue ArgumentError
147 |       args = og_args.dup
148 |       if (option_index = args.index("--#{option_name}"))
149 |         args.insert(option_index + 1, "--")
150 |         warn <<~MSG
151 |           TLDR exited in error!
152 | 
153 |           We couldn't parse #{value.inspect} as a valid #{option_name} value
154 | 
155 |           Did you mean to set --#{option_name} as the last option and without an explicit value?
156 | 
157 |           If so, you need to append ' -- ' before any paths, like:
158 | 
159 |             tldr #{args.join(" ")}
160 |         MSG
161 |         exit 1
162 |       else
163 |         raise
164 |       end
165 |     end
166 |   end
167 | end
168 | 
--------------------------------------------------------------------------------
/lib/tldr/argv_reconstructor.rb:
--------------------------------------------------------------------------------
  1 | class TLDR
  2 |   class ArgvReconstructor
  3 |     def reconstruct config, exclude:, ensure_args:, exclude_dotfile_matches:
  4 |       argv = to_cli_argv(
  5 |         config,
  6 |         CONFLAGS.keys - exclude - [
  7 |           (:seed unless config.seed_set_intentionally),
  8 |           :watch,
  9 |           :i_am_being_watched
 10 |         ],
 11 |         exclude_dotfile_matches:
 12 |       )
 13 | 
 14 |       ensure_args.each do |arg|
 15 |         argv << arg unless argv.include?(arg)
 16 |       end
 17 | 
 18 |       argv.join(" ")
 19 |     end
 20 | 
 21 |     def reconstruct_single_path_args config, path, exclude_dotfile_matches:
 22 |       argv = to_cli_argv(config, CONFLAGS.keys - [
 23 |         :seed, :parallel, :names, :fail_fast, :paths, :prepend_paths,
 24 |         :no_prepend, :exclude_paths, :watch, :i_am_being_watched
 25 |       ], exclude_dotfile_matches:)
 26 | 
 27 |       (argv + [stringify(:paths, path)]).join(" ")
 28 |     end
 29 | 
 30 |     private
 31 | 
 32 |     def to_cli_argv config, options = CONFLAGS.keys, exclude_dotfile_matches:
 33 |       defaults = Config.build_defaults(cli_defaults: true)
 34 |       defaults = defaults.merge(config.dotfile_args(config.config_path)) if exclude_dotfile_matches
 35 |       options.map { |key|
 36 |         flag = CONFLAGS[key]
 37 | 
 38 |         # Special cases
 39 |         if key == :prepend_paths
 40 |           if config.prepend_paths.map { |s| stringify(key, s) }.sort == config.paths.map { |s| stringify(:paths, s) }.sort
 41 |             # Don't print prepended tests if they're the same as the test paths
 42 |             next
 43 |           elsif config.no_prepend
 44 |             # Don't print prepended tests if they're disabled
 45 |             next
 46 |           end
 47 |         elsif key == :helper_paths && config.no_helper
 48 |           # Don't print the helper if it's disabled
 49 |           next
 50 |         elsif key == :parallel
 51 |           val = if !config.seed_set_intentionally && !config.parallel
 52 |             "--no-parallel"
 53 |           elsif !config.seed.nil? && config.seed_set_intentionally && config.parallel
 54 |             "--parallel"
 55 |           end
 56 |           next val
 57 |         elsif key == :timeout
 58 |           if config[:timeout] < 0
 59 |             next
 60 |           elsif config[:timeout] == Config::DEFAULT_TIMEOUT
 61 |             next "--timeout"
 62 |           elsif config[:timeout] != Config::DEFAULT_TIMEOUT
 63 |             next "--timeout #{config[:timeout]}"
 64 |           else
 65 |             next
 66 |           end
 67 |         elsif key == :config_path
 68 |           case config[:config_path]
 69 |           when nil then next "--no-config"
 70 |           when Config::DEFAULT_YAML_PATH then next
 71 |           else next "--config #{config[:config_path]}"
 72 |           end
 73 |         end
 74 | 
 75 |         if defaults[key] == config[key] && (key != :seed || !config.seed_set_intentionally)
 76 |           next
 77 |         elsif CONFLAGS[key]&.start_with?("--[no-]")
 78 |           case config[key]
 79 |           when false then CONFLAGS[key].gsub(/[\[\]]/, "")
 80 |           when nil || true then CONFLAGS[key].gsub("[no-]", "")
 81 |           else "#{CONFLAGS[key].gsub("[no-]", "")} #{stringify(key, config[key])}"
 82 |           end
 83 |         elsif config[key].is_a?(Array)
 84 |           config[key].map { |value| [flag, stringify(key, value)] }
 85 |         elsif config[key].is_a?(TrueClass) || config[key].is_a?(FalseClass)
 86 |           flag if config[key]
 87 |         elsif config[key].is_a?(Class)
 88 |           [flag, config[key].name]
 89 |         elsif !config[key].nil?
 90 |           [flag, stringify(key, config[key])]
 91 |         end
 92 |       }.flatten.compact
 93 |     end
 94 | 
 95 |     def stringify key, val
 96 |       if PATH_FLAGS.include?(key) && val.start_with?(Dir.pwd)
 97 |         val = val[Dir.pwd.length + 1..]
 98 |       end
 99 | 
100 |       if val.nil? || val.is_a?(Integer)
101 |         val
102 |       else
103 |         "\"#{val}\""
104 |       end
105 |     end
106 |   end
107 | end
108 | 
--------------------------------------------------------------------------------
/lib/tldr/autorun.rb:
--------------------------------------------------------------------------------
1 | require "tldr"
2 | TLDR::Run.at_exit!(TLDR::ArgvParser.new.parse(ARGV))
3 | 
--------------------------------------------------------------------------------
/lib/tldr/backtrace_filter.rb:
--------------------------------------------------------------------------------
 1 | class TLDR
 2 |   class BacktraceFilter
 3 |     BASE_PATH = __dir__.freeze
 4 | 
 5 |     def filter backtrace
 6 |       return ["No backtrace"] unless backtrace
 7 |       return backtrace.dup if $DEBUG
 8 | 
 9 |       trim_leading_frames(backtrace) ||
10 |         trim_internal_frames(backtrace) ||
11 |         backtrace.dup
12 |     end
13 | 
14 |     private
15 | 
16 |     def trim_leading_frames backtrace
17 |       if (trimmed = backtrace.take_while { |frame| meaningful?(frame) }).any?
18 |         trimmed
19 |       end
20 |     end
21 | 
22 |     def trim_internal_frames backtrace
23 |       if (trimmed = backtrace.select { |frame| meaningful?(frame) }).any?
24 |         trimmed
25 |       end
26 |     end
27 | 
28 |     def meaningful? frame
29 |       !internal?(frame)
30 |     end
31 | 
32 |     def internal? frame
33 |       frame.start_with?(BASE_PATH)
34 |     end
35 |   end
36 | 
37 |   def self.filter_backtrace backtrace
38 |     BacktraceFilter.new.filter(backtrace)
39 |   end
40 | end
41 | 
--------------------------------------------------------------------------------
/lib/tldr/class_util.rb:
--------------------------------------------------------------------------------
 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
16 | 
--------------------------------------------------------------------------------
/lib/tldr/error.rb:
--------------------------------------------------------------------------------
1 | class TLDR
2 |   class Error < StandardError; end
3 | 
4 |   class Failure < Exception; end # standard:disable Lint/InheritException
5 | 
6 |   class Skip < StandardError; end
7 | end
8 | 
--------------------------------------------------------------------------------
/lib/tldr/executor.rb:
--------------------------------------------------------------------------------
 1 | class TLDR
 2 |   class Executor
 3 |     def initialize
 4 |       @thread_pool = Concurrent::ThreadPoolExecutor.new(
 5 |         name: "tldr",
 6 |         auto_terminate: true
 7 |       )
 8 |     end
 9 | 
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
18 |     end
19 | 
20 |     private
21 | 
22 |     def run_in_sequence tests, &blk
23 |       tests.map(&blk)
24 |     end
25 | 
26 |     def run_in_parallel tests_and_groups, &blk
27 |       tests_and_groups.map { |test_or_group|
28 |         tests_to_run = if test_or_group.group?
29 |           test_or_group.tests
30 |         else
31 |           [test_or_group]
32 |         end
33 | 
34 |         unless tests_to_run.empty?
35 |           Concurrent::Promises.future_on(@thread_pool) {
36 |             tests_to_run.map(&blk)
37 |           }
38 |         end
39 |       }.compact.flat_map(&:value)
40 |     end
41 |   end
42 | end
43 | 
--------------------------------------------------------------------------------
/lib/tldr/hooks.rb:
--------------------------------------------------------------------------------
 1 | class TLDR
 2 |   module Hooks
 3 |     def setup
 4 |     end
 5 | 
 6 |     def teardown
 7 |     end
 8 |   end
 9 | end
10 | 
--------------------------------------------------------------------------------
/lib/tldr/minitest_compatibility.rb:
--------------------------------------------------------------------------------
 1 | # These methods are provided to support drop-in compatibility with Minitest. You
 2 | # can use them by mixing them into your test or into the `TLDR` base class
 3 | # itself:
 4 | #
 5 | #   class YourTest < TLDR
 6 | #     include TLDR::MinitestCompatibility
 7 | #
 8 | #     def test_something
 9 | #       # …
10 | #     end
11 | #   end
12 | #
13 | # The implementation of these methods is extremely similar or identical to those
14 | # found in minitest itself, which is Copyright © Ryan Davis, seattle.rb and
15 | # distributed under the MIT License
16 | class TLDR
17 |   module MinitestCompatibility
18 |     def capture_io &blk
19 |       Assertions.capture_io(&blk)
20 |     end
21 | 
22 |     def mu_pp obj
23 |       s = obj.inspect.encode(Encoding.default_external)
24 | 
25 |       if String === obj && (obj.encoding != Encoding.default_external ||
26 |                             !obj.valid_encoding?)
27 |         enc = "# encoding: #{obj.encoding}"
28 |         val = "#    valid: #{obj.valid_encoding?}"
29 |         "#{enc}\n#{val}\n#{s}"
30 |       else
31 |         s
32 |       end
33 |     end
34 |   end
35 | end
36 | 
--------------------------------------------------------------------------------
/lib/tldr/parallel_controls.rb:
--------------------------------------------------------------------------------
 1 | class TLDR
 2 |   # If it's not safe to run a set of tests in parallel, you can force them to
 3 |   # run in a group together (in a single worker) with `run_these_together!` in
 4 |   # your test.
 5 |   #
 6 |   # This method takes an array of tuples, where the first element is the class
 7 |   # (or its name as a string, if the class is not yet defined in the current
 8 |   # file) and the second element is the method name. If the second element is
 9 |   # nil, then all the tests on the class will be run together.
10 |   #
11 |   # Examples:
12 |   #   - `run_these_together!` will run all the tests defined on the current
13 |   #     class to be run in a group
14 |   #   - `run_these_together!([[ClassA, nil], ["ClassB", :test_1], [ClassB, :test_2]])`
15 |   #    will run all the tests defined on ClassA, and test_1 and test_2 from ClassB
16 |   #
17 |   GROUPED_TESTS = Concurrent::Array.new
18 |   def self.run_these_together! klass_method_tuples = [[self, nil]]
19 |     GROUPED_TESTS << TestGroup.new(klass_method_tuples)
20 |   end
21 | 
22 |   # This is a similar API to run_these_together! but its effect is more drastic
23 |   # Rather than running the provided (class, method) tuples in a group within a
24 |   # thread as part of a parallel run, it will reserve all tests specified by
25 |   # all calls to `dont_run_these_in_parallel!` to be run after all parallel tests have
26 |   # finished.
27 |   #
28 |   # This has an important implication! If your test suite is over TLDR's time
29 |   # limit, it means that these tests will never be run outside of CI unless you
30 |   # run them manually.
31 |   #
32 |   # Like `run_these_together!`, `dont_run_these_in_parallel!` takes an array of
33 |   # tuples, where the first element is the class (or its fully-qualified name as
34 |   # a string) and the second element is `nil` (matching all the class's test
35 |   # methods) or else one of the methods on the class.
36 |   #
37 |   # Examples:
38 |   #   - `dont_run_these_in_parallel!` will run all the tests defined on the current
39 |   #     class after all parallel tests have finished
40 |   #   - `dont_run_these_in_parallel!([[ClassA, nil], ["ClassB", :test_1], [ClassB, :test_2]])`
41 |   #    will run all the tests defined on ClassA, and test_1 and test_2 from ClassB
42 |   #
43 |   THREAD_UNSAFE_TESTS = Concurrent::Array.new
44 |   def self.dont_run_these_in_parallel! klass_method_tuples = [[self, nil]]
45 |     THREAD_UNSAFE_TESTS << TestGroup.new(klass_method_tuples)
46 |   end
47 | end
48 | 
--------------------------------------------------------------------------------
/lib/tldr/path_util.rb:
--------------------------------------------------------------------------------
 1 | class TLDR
 2 |   module PathUtil
 3 |     def self.expand_paths path_strings, globs: true
 4 |       path_strings = expand_globs(path_strings) if globs
 5 | 
 6 |       path_strings.flat_map { |path_string|
 7 |         File.directory?(path_string) ? Dir["#{path_string}/**/*.rb"] : path_string
 8 |       }.flat_map { |path_string|
 9 |         absolute_path = File.expand_path(path_string.gsub(/:.*$/, ""), Dir.pwd)
10 |         line_numbers = path_string.scan(/:(\d+)/).flatten.map(&:to_i)
11 | 
12 |         if line_numbers.any?
13 |           line_numbers.map { |line_number| Location.new(absolute_path, line_number) }
14 |         else
15 |           [Location.new(absolute_path, nil)]
16 |         end
17 |       }.uniq
18 |     end
19 | 
20 |     # Because search paths to TLDR can include line numbers (e.g. a.rb:4), we
21 |     # can't just pass everything to Dir.glob. Instead, we have to check whether
22 |     # a user-provided search path looks like a glob, and if so, expand it
23 |     #
24 |     # Globby characters specified here:
25 |     # https://ruby-doc.org/3.2.2/Dir.html#method-c-glob
26 |     def self.expand_globs search_paths
27 |       search_paths.flat_map { |search_path|
28 |         if search_path.match?(/[*?\[\]{}]/)
29 |           raise Error, "Can't combine globs and line numbers in: #{search_path}" if search_path.match?(/:(\d+)$/)
30 |           Dir[search_path]
31 |         else
32 |           search_path
33 |         end
34 |       }
35 |     end
36 | 
37 |     def self.locations_include_test? locations, test
38 |       locations.any? { |location|
39 |         location.file == test.file && (location.line.nil? || test.covers_line?(location.line))
40 |       }
41 |     end
42 | 
43 |     def self.chdir_maybe path
44 |       if path.nil?
45 |         yield
46 |       else
47 |         Dir.chdir(path) { yield }
48 |       end
49 |     end
50 |   end
51 | end
52 | 
--------------------------------------------------------------------------------
/lib/tldr/planner.rb:
--------------------------------------------------------------------------------
  1 | require "pathname"
  2 | 
  3 | class TLDR
  4 |   class Planner
  5 |     def initialize
  6 |       @strategizer = Strategizer.new
  7 |     end
  8 | 
  9 |     def plan config
 10 |       $VERBOSE = config.warnings
 11 |       search_locations = PathUtil.expand_paths(config.paths, globs: false)
 12 | 
 13 |       prepend_load_paths(config)
 14 |       require_test_helper(config)
 15 |       require_tests(search_locations)
 16 | 
 17 |       tests = gather_tests
 18 |       config.update_after_gathering_tests!(tests)
 19 |       tests_to_run = prepend(
 20 |         shuffle(
 21 |           exclude_by_path(
 22 |             exclude_by_name(
 23 |               filter_by_line(
 24 |                 filter_by_name(tests, config.names),
 25 |                 search_locations
 26 |               ),
 27 |               config.exclude_names
 28 |             ),
 29 |             config.exclude_paths
 30 |           ),
 31 |           config.seed
 32 |         ),
 33 |         config
 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)
 44 |     end
 45 | 
 46 |     private
 47 | 
 48 |     def gather_tests
 49 |       ClassUtil.gather_descendants(TLDR).flat_map { |subklass|
 50 |         ClassUtil.gather_tests(subklass)
 51 |       }
 52 |     end
 53 | 
 54 |     def prepend tests, config
 55 |       return tests if config.no_prepend
 56 |       prepended_locations = PathUtil.expand_paths(config.prepend_paths)
 57 |       prepended, rest = tests.partition { |test|
 58 |         PathUtil.locations_include_test?(prepended_locations, test)
 59 |       }
 60 |       prepended + rest
 61 |     end
 62 | 
 63 |     def shuffle tests, seed
 64 |       tests.shuffle(random: Random.new(seed))
 65 |     end
 66 | 
 67 |     def exclude_by_path tests, exclude_paths
 68 |       excluded_locations = PathUtil.expand_paths(exclude_paths)
 69 |       return tests if excluded_locations.empty?
 70 | 
 71 |       tests.reject { |test|
 72 |         PathUtil.locations_include_test?(excluded_locations, test)
 73 |       }
 74 |     end
 75 | 
 76 |     def exclude_by_name tests, exclude_names
 77 |       return tests if exclude_names.empty?
 78 | 
 79 |       name_excludes = expand_names_with_patterns(exclude_names)
 80 | 
 81 |       tests.reject { |test|
 82 |         name_excludes.any? { |filter|
 83 |           filter === test.method_name.to_s || filter === "#{test.test_class}##{test.method_name}"
 84 |         }
 85 |       }
 86 |     end
 87 | 
 88 |     def filter_by_line tests, search_locations
 89 |       line_specific_locations = search_locations.reject { |location| location.line.nil? }
 90 |       return tests if line_specific_locations.empty?
 91 | 
 92 |       tests.select { |test|
 93 |         PathUtil.locations_include_test?(line_specific_locations, test)
 94 |       }
 95 |     end
 96 | 
 97 |     def filter_by_name tests, names
 98 |       return tests if names.empty?
 99 | 
100 |       name_filters = expand_names_with_patterns(names)
101 | 
102 |       tests.select { |test|
103 |         name_filters.any? { |filter|
104 |           filter === test.method_name.to_s || filter === "#{test.test_class}##{test.method_name}"
105 |         }
106 |       }
107 |     end
108 | 
109 |     def prepend_load_paths config
110 |       config.load_paths.each do |load_path|
111 |         $LOAD_PATH.unshift(File.expand_path(load_path, Dir.pwd))
112 |       end
113 |     end
114 | 
115 |     def require_test_helper config
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
122 |     end
123 | 
124 |     def require_tests search_locations
125 |       search_locations.each do |location|
126 |         require location.file
127 |       end
128 |     end
129 | 
130 |     def expand_names_with_patterns names
131 |       names.map { |name|
132 |         if name.is_a?(String) && name =~ /^\/(.*)\/$/
133 |           Regexp.new($1)
134 |         else
135 |           name
136 |         end
137 |       }
138 |     end
139 |   end
140 | end
141 | 
--------------------------------------------------------------------------------
/lib/tldr/rake.rb:
--------------------------------------------------------------------------------
 1 | require "rake"
 2 | require "shellwords"
 3 | 
 4 | require "tldr"
 5 | 
 6 | class TLDR
 7 |   class Task
 8 |     include Rake::DSL
 9 | 
10 |     def initialize name: "tldr", config: Config.new
11 |       define name, config
12 |     end
13 | 
14 |     private
15 | 
16 |     def define name, task_config
17 |       desc "Run #{name} tests (use TLDR_OPTS or .tldr.yml to configure)"
18 |       task name do
19 |         argv = Shellwords.shellwords(merge_env_opts(task_config).to_full_args)
20 |         begin
21 |           TLDR::Run.cli(argv)
22 |         rescue SystemExit => e
23 |           fail "TLDR task #{name} failed with status #{e.status}" unless e.status == 0
24 |         end
25 |       end
26 |     end
27 | 
28 |     def merge_env_opts task_config
29 |       if ENV["TLDR_OPTS"]
30 |         env_argv = Shellwords.shellwords(ENV["TLDR_OPTS"])
31 |         opts_config = ArgvParser.new.parse(env_argv, {
32 |           config_intended_for_merge_only: true
33 |         })
34 |         task_config.merge(opts_config)
35 |       else
36 |         task_config
37 |       end
38 |     end
39 |   end
40 | end
41 | 
42 | # Create the default tldr task for users
43 | TLDR::Task.new
44 | 
--------------------------------------------------------------------------------
/lib/tldr/reporters.rb:
--------------------------------------------------------------------------------
1 | require "tldr/reporters/icon_provider"
2 | 
3 | require "tldr/reporters/base"
4 | require "tldr/reporters/default"
5 | 
--------------------------------------------------------------------------------
/lib/tldr/reporters/base.rb:
--------------------------------------------------------------------------------
 1 | class TLDR
 2 |   module Reporters
 3 |     class Base
 4 |       def initialize config, out = $stdout, err = $stderr
 5 |         out.sync = true
 6 |         err.sync = true
 7 | 
 8 |         @config = config
 9 |         @out = out
10 |         @err = err
11 |       end
12 | 
13 |       # Will be called before any tests are run
14 |       def before_suite tests
15 |       end
16 | 
17 |       # Will be called after each test, unless the run has already been aborted
18 |       def after_test test_result
19 |       end
20 | 
21 |       # Will be called after all tests have run, unless the run was aborted
22 |       #
23 |       # Exactly ONE of `after_suite`, `after_tldr`, or `after_fail_fast` will be called
24 |       def after_suite test_results
25 |       end
26 | 
27 |       # Called after the suite-wide time limit expires and the run is aborted
28 |       def after_tldr planned_tests, wip_tests, test_results
29 |       end
30 | 
31 |       # Called after the first test fails when --fail-fast is enabled, aborting the run
32 |       def after_fail_fast planned_tests, wip_tests, test_results, last_result
33 |       end
34 |     end
35 |   end
36 | end
37 | 
--------------------------------------------------------------------------------
/lib/tldr/reporters/default.rb:
--------------------------------------------------------------------------------
  1 | class TLDR
  2 |   module Reporters
  3 |     class Default < Base
  4 |       def initialize config, out = $stdout, err = $stderr
  5 |         super
  6 |         @icons = @config.emoji ? IconProvider::Emoji.new : IconProvider::Base.new
  7 |       end
  8 | 
  9 |       def before_suite tests
 10 |         clear_screen_if_being_watched!
 11 |         @suite_start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
 12 |         @out.print <<~MSG
 13 |           Command: #{tldr_command} #{@config.to_full_args}
 14 |           #{@icons.rpad(:seed)}#{CONFLAGS[:seed]} #{@config.seed}
 15 | 
 16 |           #{@icons.rpad(:run)}Running:
 17 | 
 18 |         MSG
 19 |       end
 20 | 
 21 |       def after_test result
 22 |         output = case result.type
 23 |         when :success then @icons.success
 24 |         when :skip then @icons.skip
 25 |         when :failure then @icons.failure
 26 |         when :error then @icons.error
 27 |         end
 28 |         if @config.verbose
 29 |           @out.puts "#{output} #{result.type.capitalize} - #{describe(result.test, result.relevant_location)}"
 30 |         else
 31 |           @out.print output
 32 |         end
 33 |       end
 34 | 
 35 |       def after_tldr planned_tests, wip_tests, test_results
 36 |         stop_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
 37 | 
 38 |         @out.print @icons.tldr
 39 |         @err.print "\n\n"
 40 | 
 41 |         if @config.yes_i_know
 42 |           @err.print "#{@icons.rpad(:alarm)}TLDR after completing #{test_results.size} of #{planned_tests.size} tests! Print full summary by omitting --yes-i-know"
 43 |         else
 44 |           wrap_in_horizontal_rule do
 45 |             @err.print [
 46 |               "too long; didn't run!",
 47 |               "#{@icons.rpad(: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.",
 48 |               (<<~WIP.chomp if wip_tests.any?),
 49 |                 #{@icons.rpad(:wip)}#{plural(wip_tests.size, "test was", "tests were")} cancelled in progress:
 50 |                 #{wip_tests.map { |wip_test| "  #{time_diff(wip_test.start_time, stop_time)}ms - #{describe(wip_test.test)}#{print_wip_backtrace(wip_test, indent: "    ") if @config.print_interrupted_test_backtraces}" }.join("\n")}
 51 |               WIP
 52 |               (<<~SLOW.chomp if test_results.any?),
 53 |                 #{@icons.rpad(:slow)}Your #{[10, test_results.size].min} slowest completed tests:
 54 |                 #{test_results.sort_by(&:runtime).last(10).reverse.map { |result| "  #{result.runtime}ms - #{describe(result.test)}" }.join("\n")}
 55 |               SLOW
 56 |               describe_tests_that_didnt_finish(planned_tests, test_results),
 57 |               "#{@icons.rpad(:not_run)}Suppress this summary with --yes-i-know"
 58 |             ].compact.join("\n\n")
 59 |           end
 60 |         end
 61 | 
 62 |         after_suite(test_results)
 63 |       end
 64 | 
 65 |       def after_fail_fast planned_tests, wip_tests, test_results, last_result
 66 |         unrun_tests = planned_tests - test_results.map(&:test) - wip_tests.map(&:test)
 67 | 
 68 |         @err.print "\n\n"
 69 |         wrap_in_horizontal_rule do
 70 |           @err.print [
 71 |             "Failing fast after #{describe(last_result.test, last_result.relevant_location)} #{last_result.error? ? "errored" : "failed"}.",
 72 |             ("#{@icons.rpad(:wip)}#{plural(wip_tests.size, "test was", "tests were")} cancelled in progress." if wip_tests.any?),
 73 |             ("#{@icons.rpad(:not_run)} #{plural(unrun_tests.size, "test was", "tests were")} not run at all." if unrun_tests.any?),
 74 |             describe_tests_that_didnt_finish(planned_tests, test_results)
 75 |           ].compact.join("\n\n")
 76 |         end
 77 | 
 78 |         after_suite(test_results)
 79 |       end
 80 | 
 81 |       def after_suite test_results
 82 |         duration = time_diff(@suite_start_time)
 83 |         test_results = test_results.sort_by { |result| [result.test.location.file, result.test.location.line] }
 84 | 
 85 |         @err.print summarize_failures(test_results).join("\n\n")
 86 | 
 87 |         @out.print summarize_skips(test_results).join("\n")
 88 | 
 89 |         @out.print "\n\n"
 90 |         @out.print "Finished in #{duration}ms."
 91 | 
 92 |         @out.print "\n\n"
 93 |         class_count = test_results.uniq { |result| result.test.test_class }.size
 94 |         test_count = test_results.size
 95 |         @out.print [
 96 |           plural(class_count, "test class", "test classes"),
 97 |           plural(test_count, "test method"),
 98 |           plural(test_results.count(&:failure?), "failure"),
 99 |           plural(test_results.count(&:error?), "error"),
100 |           plural(test_results.count(&:skip?), "skip")
101 |         ].join(", ")
102 | 
103 |         @out.print "\n"
104 |       end
105 | 
106 |       private
107 | 
108 |       def time_diff start, stop = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
109 |         ((stop - start) / 1000.0).round
110 |       end
111 | 
112 |       def summarize_failures results
113 |         failures = results.select { |result| result.failing? }
114 |         return failures if failures.empty?
115 | 
116 |         ["\n\nFailing tests:"] + failures.map.with_index { |result, i| summarize_result(result, i) }
117 |       end
118 | 
119 |       def summarize_result result, index
120 |         [
121 |           "#{index + 1}) #{describe(result.test, result.relevant_location)} #{result.failure? ? "failed" : "errored"}:",
122 |           result.error.message.chomp,
123 |           "\n  Re-run this test:",
124 |           "    #{tldr_command} #{@config.to_single_path_args(result.test.location.locator, exclude_dotfile_matches: true)}\n",
125 |           (TLDR.filter_backtrace(result.error.backtrace).join("\n") if @config.verbose)
126 |         ].compact.reject(&:empty?).join("\n").strip
127 |       end
128 | 
129 |       def summarize_skips results
130 |         skips = results.select { |result| result.skip? }
131 |         return skips if skips.empty?
132 | 
133 |         ["\n\nSkipped tests:\n"] + skips.map { |result| "  - #{describe(result.test)}" }
134 |       end
135 | 
136 |       def describe test, location = test.location
137 |         "#{test.test_class}##{test.method_name} [#{location.locator}]"
138 |       end
139 | 
140 |       def print_wip_backtrace wip_test, indent: ""
141 |         return unless wip_test.backtrace_at_exit
142 | 
143 |         "\n#{indent}Backtrace at the point of cancellation:\n#{indent}#{wip_test.backtrace_at_exit.join("\n#{indent}")}"
144 |       end
145 | 
146 |       def plural count, singular, plural = "#{singular}s"
147 |         "#{count} #{(count == 1) ? singular : plural}"
148 |       end
149 | 
150 |       def wrap_in_horizontal_rule
151 |         rule = @icons.alarm + "=" * 20 + " ABORTED RUN " + "=" * 20 + @icons.alarm
152 |         @err.print "#{rule}\n\n"
153 |         yield
154 |         @err.print "\n\n#{rule}"
155 |       end
156 | 
157 |       def describe_tests_that_didnt_finish planned_tests, test_results
158 |         unrun = planned_tests - test_results.map(&:test)
159 |         return if unrun.empty?
160 | 
161 |         unrun_locators = consolidate(unrun)
162 |         failed = test_results.select(&:failing?).map(&:test)
163 |         failed_locators = consolidate(failed, exclude: unrun_locators)
164 |         suggested_locators = unrun_locators + [
165 |           ("--comment \"Also include #{plural(failed.size, "test")} that failed:\"" if failed_locators.any?)
166 |         ].compact + failed_locators
167 |         <<~MSG
168 |           #{@icons.rpad(:rock_on)}Run the #{plural(unrun.size, "test")} that didn't finish:
169 |             #{tldr_command} #{@config.to_full_args(exclude: [:paths], exclude_dotfile_matches: true)} #{suggested_locators.join(" \\\n    ")}
170 |         MSG
171 |       end
172 | 
173 |       def consolidate tests, exclude: []
174 |         tests.group_by(&:file).map { |_, tests|
175 |           "\"#{tests.first.location.relative}:#{tests.map(&:line).uniq.sort.join(":")}\""
176 |         }.uniq - exclude
177 |       end
178 | 
179 |       def tldr_command
180 |         "#{"bundle exec " if defined?(Bundler)}tldr"
181 |       end
182 | 
183 |       def clear_screen_if_being_watched!
184 |         if @config.i_am_being_watched
185 |           @out.print "\e[2J\e[f"
186 |         end
187 |       end
188 |     end
189 |   end
190 | end
191 | 
--------------------------------------------------------------------------------
/lib/tldr/reporters/icon_provider.rb:
--------------------------------------------------------------------------------
  1 | module IconProvider
  2 |   class Base
  3 |     def success
  4 |       "."
  5 |     end
  6 | 
  7 |     def failure
  8 |       "F"
  9 |     end
 10 | 
 11 |     def error
 12 |       "E"
 13 |     end
 14 | 
 15 |     def skip
 16 |       "S"
 17 |     end
 18 | 
 19 |     def tldr
 20 |       "!"
 21 |     end
 22 | 
 23 |     def run
 24 |       ""
 25 |     end
 26 | 
 27 |     def wip
 28 |       ""
 29 |     end
 30 | 
 31 |     def slow
 32 |       ""
 33 |     end
 34 | 
 35 |     def not_run
 36 |       ""
 37 |     end
 38 | 
 39 |     def alarm
 40 |       ""
 41 |     end
 42 | 
 43 |     def rock_on
 44 |       ""
 45 |     end
 46 | 
 47 |     def seed
 48 |       ""
 49 |     end
 50 | 
 51 |     def rpad(icon_name)
 52 |       icon = send(icon_name)
 53 |       if icon.nil? || icon.size == 0
 54 |         icon
 55 |       else
 56 |         "#{icon} "
 57 |       end
 58 |     end
 59 |   end
 60 | 
 61 |   class Emoji < Base
 62 |     def success
 63 |       "😁"
 64 |     end
 65 | 
 66 |     def failure
 67 |       "😡"
 68 |     end
 69 | 
 70 |     def error
 71 |       "🤬"
 72 |     end
 73 | 
 74 |     def skip
 75 |       "🫥"
 76 |     end
 77 | 
 78 |     def tldr
 79 |       "🥵"
 80 |     end
 81 | 
 82 |     def run
 83 |       "🏃"
 84 |     end
 85 | 
 86 |     def wip
 87 |       "🙅"
 88 |     end
 89 | 
 90 |     def slow
 91 |       "🐢"
 92 |     end
 93 | 
 94 |     def not_run
 95 |       "🙈"
 96 |     end
 97 | 
 98 |     def alarm
 99 |       "🚨"
100 |     end
101 | 
102 |     def rock_on
103 |       "🤘"
104 |     end
105 | 
106 |     def seed
107 |       "🌱"
108 |     end
109 |   end
110 | end
111 | 
--------------------------------------------------------------------------------
/lib/tldr/ruby_util.rb:
--------------------------------------------------------------------------------
 1 | class TLDR
 2 |   module RubyUtil
 3 |     def self.version
 4 |       @version ||= Gem::Version.new(RUBY_VERSION)
 5 |     end
 6 | 
 7 |     def self.parsing_with_prism?
 8 |       @parsing_with_prism ||= RubyVM::InstructionSequence.compile("").to_a[4][:parser] == :prism
 9 |     end
10 | 
11 |     def self.find_prism_def_node_for(method)
12 |       require "prism"
13 | 
14 |       iseq = RubyVM::InstructionSequence.of(method).to_a
15 |       method_metadata = iseq[4]
16 |       method_name = iseq[5]
17 | 
18 |       file_path, line_number = method.source_location
19 |       parse_prism_ast(file_path).breadth_first_search { |node|
20 |         node.type == :def_node &&
21 |           line_number == node.start_line &&
22 |           method_name == node.name.to_s &&
23 |           method_metadata[:code_location] == [node.start_line, node.start_column, node.end_line, node.end_column]
24 |       }
25 |     end
26 | 
27 |     def self.parse_prism_ast(file_path)
28 |       @prism_ast = Thread.current[:prism_parse_results] ||= {}
29 |       @prism_ast[file_path] ||= Prism.parse(File.read(file_path)).value
30 |     end
31 |   end
32 | end
33 | 
--------------------------------------------------------------------------------
/lib/tldr/runner.rb:
--------------------------------------------------------------------------------
  1 | require "irb"
  2 | 
  3 | class TLDR
  4 |   class Runner
  5 |     def initialize
  6 |       @executor = Executor.new
  7 |       @wip = Concurrent::Array.new
  8 |       @results = Concurrent::Array.new
  9 |       @run_aborted = Concurrent::AtomicBoolean.new(false)
 10 |     end
 11 | 
 12 |     def instantiate_reporter config
 13 |       begin
 14 |         reporter_class = Kernel.const_get(config.reporter)
 15 |       rescue NameError
 16 |         raise Error, "Unknown reporter '#{config.reporter}' (are you sure it was loaded by your test or helper?)"
 17 |       end
 18 |       if reporter_class.is_a?(Class)
 19 |         if reporter_class.instance_method(:initialize).parameters.any? { |type, _| [:req, :opt, :rest].include?(type) }
 20 |           reporter_class.new(config)
 21 |         else
 22 |           reporter_class.new
 23 |         end
 24 |       else
 25 |         raise Error, "Reporter '#{config.reporter}' expected to be a class, but was a #{reporter_class.class}"
 26 |       end
 27 |     end
 28 | 
 29 |     def run config, plan
 30 |       @wip.clear
 31 |       @results.clear
 32 |       reporter = instantiate_reporter(config)
 33 |       reporter.before_suite(plan.tests) if reporter.respond_to?(:before_suite)
 34 | 
 35 |       time_bomb = Thread.new {
 36 |         next if config.timeout < 0
 37 | 
 38 |         explode = proc do
 39 |           next if @run_aborted.true?
 40 |           @run_aborted.make_true
 41 |           @wip.each(&:capture_backtrace_at_exit)
 42 |           reporter.after_tldr(plan.tests, @wip.dup, @results.dup) if reporter.respond_to?(:after_tldr)
 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
 50 |         end
 51 | 
 52 |         sleep(config.timeout)
 53 | 
 54 |         # Don't hard-kill the runner if user is debugging, it'll
 55 |         # screw up their terminal slash be a bad time
 56 |         if IRB.CurrentContext
 57 |           IRB.conf[:AT_EXIT] << explode
 58 |         else
 59 |           explode.call
 60 |         end
 61 |       }
 62 | 
 63 |       results = @executor.execute(plan) { |test|
 64 |         run_test(test, config, plan, reporter)
 65 |       }.tap do
 66 |         time_bomb.kill
 67 |       end
 68 | 
 69 |       unless @run_aborted.true?
 70 |         reporter.after_suite(results) if reporter.respond_to?(:after_suite)
 71 |         exit(exit_code(results, config))
 72 |       end
 73 |     end
 74 | 
 75 |     private
 76 | 
 77 |     def run_test test, config, plan, reporter
 78 |       e = nil
 79 |       start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
 80 |       wip_test = WIPTest.new(test, start_time, Thread.current)
 81 |       @wip << wip_test
 82 |       runtime = time_it(start_time) do
 83 |         instance = test.test_class.new
 84 |         instance.setup if instance.respond_to?(:setup)
 85 |         if instance.respond_to?(:around)
 86 |           did_run = false
 87 |           instance.around {
 88 |             did_run = true
 89 |             instance.send(test.method_name)
 90 |           }
 91 |           raise Error, "#{test.test_class}#around failed to yield or call the passed test block" unless did_run
 92 |         else
 93 |           instance.send(test.method_name)
 94 |         end
 95 |         instance.teardown if instance.respond_to?(:teardown)
 96 |       rescue Skip, Failure, StandardError => e
 97 |       end
 98 |       TestResult.new(test, e, runtime).tap do |result|
 99 |         next if @run_aborted.true?
100 |         @results << result
101 |         @wip.delete(wip_test)
102 |         reporter.after_test(result) if reporter.respond_to?(:after_test)
103 |         fail_fast(reporter, plan, result) if result.failing? && config.fail_fast
104 |       end
105 |     end
106 | 
107 |     def fail_fast reporter, plan, fast_failed_result
108 |       unless @run_aborted.true?
109 |         @run_aborted.make_true
110 |         abort = proc do
111 |           reporter.after_fail_fast(plan.tests, @wip.dup, @results.dup, fast_failed_result) if reporter.respond_to?(:after_fail_fast)
112 |           exit!(exit_code([fast_failed_result]))
113 |         end
114 | 
115 |         if IRB.CurrentContext
116 |           IRB.conf[:AT_EXIT] << abort
117 |         else
118 |           abort.call
119 |         end
120 |       end
121 |     end
122 | 
123 |     def time_it start
124 |       yield
125 |       ((Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond) - start) / 1000.0).round
126 |     end
127 | 
128 |     def exit_code results, config
129 |       if results.any? { |result| result.error? }
130 |         2
131 |       elsif results.any? { |result| result.failure? }
132 |         config.exit_2_on_failure ? 2 : 1
133 |       else
134 |         0
135 |       end
136 |     end
137 |   end
138 | end
139 | 
--------------------------------------------------------------------------------
/lib/tldr/skippable.rb:
--------------------------------------------------------------------------------
1 | class TLDR
2 |   module Skippable
3 |     def skip message = ""
4 |       raise Skip, message
5 |     end
6 |   end
7 | end
8 | 
--------------------------------------------------------------------------------
/lib/tldr/sorbet_compatibility.rb:
--------------------------------------------------------------------------------
 1 | class TLDR
 2 |   class SorbetCompatibility
 3 |     def self.unwrap_method method
 4 |       return method unless defined? ::T::Private::Methods
 5 | 
 6 |       sig_or_method = ::T::Private::Methods.signature_for_method(method) || method
 7 | 
 8 |       if sig_or_method.is_a?(Method) || sig_or_method.is_a?(UnboundMethod)
 9 |         sig_or_method
10 |       else # it's a T::Private::Methods::Signature
11 |         sig_or_method.method
12 |       end
13 |     end
14 |   end
15 | end
16 | 
--------------------------------------------------------------------------------
/lib/tldr/strategizer.rb:
--------------------------------------------------------------------------------
 1 | class TLDR
 2 |   class Strategizer
 3 |     Strategy = Struct.new(:parallel?, :prepend_sequential_tests,
 4 |       :parallel_tests_and_groups, :append_sequential_tests,
 5 |       keyword_init: true)
 6 | 
 7 |     # Combine all discovered test methods with any methods grouped by run_these_together!
 8 |     #
 9 |     # Priorities:
10 |     #   - Map over tests to build out groups in order to retain shuffle order
11 |     #     (group will run in position of first test in the group)
12 |     #   - If a test is in multiple groups, only run it once
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 | 
16 |       thread_unsafe_tests, thread_safe_tests = partition_unsafe(all_tests, thread_unsafe_test_groups)
17 |       prepend_sequential_tests, append_sequential_tests = partition_prepend(thread_unsafe_tests, config)
18 | 
19 |       grouped_tests = prepare_run_together_groups(run_these_together_groups, thread_safe_tests, append_sequential_tests)
20 |       already_included_groups = []
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)
24 |             next
25 |           elsif (other = already_included_groups.find { |other| (group.tests & other.tests).any? })
26 |             other.tests |= group.tests
27 |             next
28 |           else
29 |             already_included_groups << group
30 |             group
31 |           end
32 |         else
33 |           test
34 |         end
35 |       }.compact
36 |       Strategy.new(
37 |         parallel?: true,
38 |         prepend_sequential_tests:,
39 |         parallel_tests_and_groups:,
40 |         append_sequential_tests:
41 |       )
42 |     end
43 | 
44 |     private
45 | 
46 |     def run_sequentially? all_tests, config
47 |       all_tests.size < 2 || !config.parallel
48 |     end
49 | 
50 |     def partition_unsafe tests, thread_unsafe_test_groups
51 |       tests.partition { |test|
52 |         thread_unsafe_test_groups.any? { |group| group.tests.include?(test) }
53 |       }
54 |     end
55 | 
56 |     # Sadly duplicative with Planner.rb, necessitating the extraction of PathUtil
57 |     # Suboptimal, but we do indeed need to do this work in two places ¯\_(ツ)_/¯
58 |     def partition_prepend thread_unsafe_tests, config
59 |       prepend_paths = config.no_prepend ? [] : config.prepend_paths
60 |       locations = PathUtil.expand_paths(prepend_paths)
61 | 
62 |       thread_unsafe_tests.partition { |test|
63 |         PathUtil.locations_include_test?(locations, test)
64 |       }
65 |     end
66 | 
67 |     def prepare_run_together_groups run_these_together_groups, thread_safe_tests, thread_unsafe_tests
68 |       grouped_tests = run_these_together_groups.map(&:dup)
69 | 
70 |       grouped_tests.each do |group|
71 |         group.tests = group.tests.select { |test|
72 |           thread_safe_tests.include?(test) && !thread_unsafe_tests.include?(test)
73 |         }
74 |       end
75 | 
76 |       grouped_tests.reject { |group| group.tests.size < 2 }
77 |     end
78 |   end
79 | end
80 | 
--------------------------------------------------------------------------------
/lib/tldr/value.rb:
--------------------------------------------------------------------------------
1 | require "tldr/value/config"
2 | require "tldr/value/plan"
3 | require "tldr/value/test"
4 | require "tldr/value/wip_test"
5 | require "tldr/value/test_result"
6 | require "tldr/value/location"
7 | require "tldr/value/test_group"
8 | 
--------------------------------------------------------------------------------
/lib/tldr/value/config.rb:
--------------------------------------------------------------------------------
  1 | class TLDR
  2 |   CONFLAGS = {
  3 |     timeout: "--[no-]timeout",
  4 |     watch: "--watch",
  5 |     fail_fast: "--fail-fast",
  6 |     parallel: "--[no-]parallel",
  7 |     seed: "--seed",
  8 |     names: "--name",
  9 |     exclude_names: "--exclude-name",
 10 |     exclude_paths: "--exclude-path",
 11 |     helper_paths: "--helper",
 12 |     no_helper: "--no-helper",
 13 |     prepend_paths: "--prepend",
 14 |     no_prepend: "--no-prepend",
 15 |     load_paths: "--load-path",
 16 |     base_path: "--base-path",
 17 |     config_path: "--[no-]config",
 18 |     reporter: "--reporter",
 19 |     emoji: "--[no-]emoji",
 20 |     warnings: "--[no-]warnings",
 21 |     verbose: "--verbose",
 22 |     yes_i_know: "--yes-i-know",
 23 |     print_interrupted_test_backtraces: "--print-interrupted-test-backtraces",
 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",
 27 |     paths: nil
 28 |   }.freeze
 29 | 
 30 |   PATH_FLAGS = [:paths, :helper_paths, :load_paths, :prepend_paths, :exclude_paths].freeze
 31 |   MOST_RECENTLY_MODIFIED_TAG = "MOST_RECENTLY_MODIFIED".freeze
 32 |   CONFIG_ATTRIBUTES = [
 33 |     :timeout, :watch, :fail_fast, :parallel, :seed, :names, :exclude_names,
 34 |     :exclude_paths, :helper_paths, :no_helper, :prepend_paths, :no_prepend,
 35 |     :load_paths, :base_path, :config_path, :reporter, :emoji, :warnings,
 36 |     :verbose, :yes_i_know, :print_interrupted_test_backtraces,
 37 |     :i_am_being_watched, :exit_0_on_timeout, :exit_2_on_failure, :paths,
 38 |     # Internal properties
 39 |     :config_intended_for_merge_only, :seed_set_intentionally, :cli_defaults
 40 |   ].freeze
 41 | 
 42 |   Config = Struct.new(*CONFIG_ATTRIBUTES, keyword_init: true) do
 43 |     def initialize(**args)
 44 |       @argv_reconstructor = ArgvReconstructor.new
 45 | 
 46 |       original_base_path = Dir.pwd
 47 |       unless args[:config_intended_for_merge_only]
 48 |         change_working_directory_because_i_am_bad_and_i_should_feel_bad!(args[:base_path])
 49 |         args = merge_dotfile_args(args) unless args[:config_path].nil?
 50 |       end
 51 |       args = undefault_parallel_if_seed_set(args)
 52 |       unless args[:config_intended_for_merge_only]
 53 |         args = merge_defaults(args)
 54 |         revert_working_directory_change_because_itll_ruin_everything!(original_base_path)
 55 |       end
 56 | 
 57 |       super
 58 |     end
 59 | 
 60 |     # These are for internal tracking and resolved at initialization-time
 61 |     undef_method :config_intended_for_merge_only=, :seed_set_intentionally=,
 62 |       # These must be set when the Config is first initialized
 63 |       :cli_defaults=, :config_path=, :base_path=
 64 | 
 65 |     def self.build_defaults cli_defaults: true
 66 |       common = {
 67 |         timeout: -1,
 68 |         watch: false,
 69 |         fail_fast: false,
 70 |         parallel: true,
 71 |         seed: rand(10_000),
 72 |         names: [],
 73 |         exclude_names: [],
 74 |         exclude_paths: [],
 75 |         no_helper: false,
 76 |         no_prepend: false,
 77 |         base_path: nil,
 78 |         reporter: "TLDR::Reporters::Default",
 79 |         emoji: false,
 80 |         warnings: true,
 81 |         verbose: false,
 82 |         yes_i_know: false,
 83 |         print_interrupted_test_backtraces: false,
 84 |         i_am_being_watched: false,
 85 |         exit_0_on_timeout: false,
 86 |         exit_2_on_failure: false
 87 |       }
 88 | 
 89 |       if cli_defaults
 90 |         common.merge(
 91 |           helper_paths: ["test/helper.rb"],
 92 |           prepend_paths: [MOST_RECENTLY_MODIFIED_TAG],
 93 |           load_paths: ["lib", "test"],
 94 |           config_path: nil,
 95 |           paths: Dir["test/**/*_test.rb", "test/**/test_*.rb"]
 96 |         )
 97 |       else
 98 |         common.merge(
 99 |           helper_paths: [],
100 |           prepend_paths: [],
101 |           load_paths: [],
102 |           config_path: Config::DEFAULT_YAML_PATH, # ArgvParser#parse will set this default and if it sets nil that is intentionally blank b/c --no-config
103 |           paths: []
104 |         )
105 |       end
106 |     end
107 | 
108 |     def undefault_parallel_if_seed_set args
109 |       args.merge(
110 |         parallel: (args[:parallel].nil? ? args[:seed].nil? : args[:parallel]),
111 |         seed_set_intentionally: !args[:seed].nil?
112 |       )
113 |     end
114 | 
115 |     def merge_defaults user_args
116 |       merged_args = user_args.dup
117 |       defaults = Config.build_defaults(cli_defaults: merged_args[:cli_defaults])
118 | 
119 |       # Arrays
120 |       [:names, :exclude_names, :exclude_paths, :helper_paths, :prepend_paths, :load_paths, :paths].each do |key|
121 |         merged_args[key] = defaults[key] if merged_args[key].nil? || merged_args[key].empty?
122 |       end
123 | 
124 |       # Booleans
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|
126 |         merged_args[key] = defaults[key] if merged_args[key].nil?
127 |       end
128 | 
129 |       # Values
130 |       [:timeout, :seed, :base_path, :config_path, :reporter].each do |key|
131 |         merged_args[key] ||= defaults[key]
132 |       end
133 | 
134 |       merged_args
135 |     end
136 | 
137 |     def merge other
138 |       this_config = to_h
139 |       kwargs = this_config.merge(
140 |         other.to_h.compact.except(:config_intended_for_merge_only)
141 |       )
142 |       Config.new(**kwargs)
143 |     end
144 | 
145 |     # We needed this hook (to be called by the planner), because we can't know
146 |     # the default prepend location until we have all the resolved test paths,
147 |     # so we have to mutate it after the fact.
148 |     def update_after_gathering_tests! tests
149 |       return unless prepend_paths.include?(MOST_RECENTLY_MODIFIED_TAG)
150 | 
151 |       self.prepend_paths = prepend_paths.map { |path|
152 |         if path == MOST_RECENTLY_MODIFIED_TAG
153 |           most_recently_modified_test_file(tests)
154 |         else
155 |           path
156 |         end
157 |       }.compact
158 |     end
159 | 
160 |     def to_full_args exclude: [], ensure_args: [], exclude_dotfile_matches: false
161 |       @argv_reconstructor.reconstruct(self, exclude:, ensure_args:, exclude_dotfile_matches:)
162 |     end
163 | 
164 |     def to_single_path_args path, exclude_dotfile_matches: false
165 |       @argv_reconstructor.reconstruct_single_path_args(self, path, exclude_dotfile_matches:)
166 |     end
167 | 
168 |     def dotfile_args config_path
169 |       return {} unless File.exist?(config_path)
170 | 
171 |       @dotfile_args ||= YamlParser.new.parse(config_path)
172 |     end
173 | 
174 |     private
175 | 
176 |     def most_recently_modified_test_file tests
177 |       return if tests.empty?
178 | 
179 |       tests.max_by { |test| File.mtime(test.file) }.file
180 |     end
181 | 
182 |     # If the user sets a custom base path, we need to change the working directory
183 |     # ASAP, even before globbing to find default paths of tests. If there is
184 |     # a way to change all of our Dir.glob calls to be relative to base_path
185 |     # without a loss in accuracy, would love to not have to use Dir.chdir!
186 |     def change_working_directory_because_i_am_bad_and_i_should_feel_bad! base_path
187 |       Dir.chdir(base_path) unless base_path.nil?
188 |     end
189 | 
190 |     def revert_working_directory_change_because_itll_ruin_everything! original_base_path
191 |       Dir.chdir(original_base_path) unless Dir.pwd == original_base_path
192 |     end
193 | 
194 |     def merge_dotfile_args args
195 |       dotfile_args(args[:config_path]).merge(args)
196 |     end
197 |   end
198 | 
199 |   Config::DEFAULT_YAML_PATH = ".tldr.yml"
200 |   Config::DEFAULT_TIMEOUT = 1.8
201 | end
202 | 
--------------------------------------------------------------------------------
/lib/tldr/value/location.rb:
--------------------------------------------------------------------------------
 1 | class TLDR
 2 |   Location = Struct.new(:file, :line) do
 3 |     def relative
 4 |       if file.start_with?(Dir.pwd)
 5 |         file[Dir.pwd.length + 1..]
 6 |       else
 7 |         file
 8 |       end
 9 |     end
10 | 
11 |     def locator
12 |       "#{relative}:#{line}"
13 |     end
14 |   end
15 | end
16 | 
--------------------------------------------------------------------------------
/lib/tldr/value/plan.rb:
--------------------------------------------------------------------------------
1 | class TLDR
2 |   Plan = Struct.new(:tests, :strategy)
3 | end
4 | 
--------------------------------------------------------------------------------
/lib/tldr/value/test.rb:
--------------------------------------------------------------------------------
 1 | class TLDR
 2 |   Test = Struct.new(:test_class, :method_name) do
 3 |     attr_reader :file, :line, :location
 4 | 
 5 |     def initialize(*args)
 6 |       super
 7 |       @file, @line = SorbetCompatibility.unwrap_method(test_class.instance_method(method_name)).source_location
 8 |       @location = Location.new(file, line)
 9 |     end
10 | 
11 |     # Test exact match starting line condition first to save us a potential re-parsing to look up end_line
12 |     def covers_line? l
13 |       line == l || l.between?(line, end_line)
14 |     end
15 | 
16 |     def group?
17 |       false
18 |     end
19 | 
20 |     private
21 | 
22 |     # Memoizing at call time, because re-parsing isn't free and isn't usually necessary
23 |     def end_line
24 |       @end_line ||= begin
25 |         test_method = SorbetCompatibility.unwrap_method(test_class.instance_method(method_name))
26 |         if RubyUtil.parsing_with_prism?
27 |           RubyUtil.find_prism_def_node_for(test_method)&.end_line || -1
28 |         else
29 |           RubyVM::AbstractSyntaxTree.of(test_method).last_lineno
30 |         end
31 |       end
32 |     end
33 |   end
34 | end
35 | 
--------------------------------------------------------------------------------
/lib/tldr/value/test_group.rb:
--------------------------------------------------------------------------------
 1 | class TLDR
 2 |   TestGroup = Struct.new(:configuration) do
 3 |     attr_writer :tests
 4 | 
 5 |     def tests
 6 |       @tests ||= configuration.flat_map { |(klass, method)|
 7 |         klass = Kernel.const_get(klass) if klass.is_a?(String)
 8 |         if method.nil?
 9 |           ([klass] + ClassUtil.gather_descendants(klass)).flat_map { |klass|
10 |             ClassUtil.gather_tests(klass)
11 |           }
12 |         else
13 |           Test.new(klass, method)
14 |         end
15 |       }
16 |     end
17 | 
18 |     def group?
19 |       true
20 |     end
21 |   end
22 | end
23 | 
--------------------------------------------------------------------------------
/lib/tldr/value/test_result.rb:
--------------------------------------------------------------------------------
 1 | class TLDR
 2 |   TestResult = Struct.new(:test, :error, :runtime) do
 3 |     attr_reader :type, :error_location
 4 | 
 5 |     def initialize(*args)
 6 |       super
 7 |       @type = determine_type
 8 |       @error_location = determine_error_location
 9 |     end
10 | 
11 |     def passing?
12 |       success? || skip?
13 |     end
14 | 
15 |     def failing?
16 |       !passing?
17 |     end
18 | 
19 |     def success?
20 |       type == :success
21 |     end
22 | 
23 |     def skip?
24 |       type == :skip
25 |     end
26 | 
27 |     def failure?
28 |       type == :failure
29 |     end
30 | 
31 |     def error?
32 |       type == :error
33 |     end
34 | 
35 |     def relevant_location
36 |       error_location || test.location
37 |     end
38 | 
39 |     private
40 | 
41 |     def determine_type
42 |       if error.nil?
43 |         :success
44 |       elsif error.is_a?(Failure)
45 |         :failure
46 |       elsif error.is_a?(Skip)
47 |         :skip
48 |       else
49 |         :error
50 |       end
51 |     end
52 | 
53 |     def determine_error_location
54 |       return if error.nil?
55 | 
56 |       raised_at = TLDR.filter_backtrace(error.backtrace).first
57 |       if (raise_matches = raised_at.match(/^(.*):(\d+):in .*$/))
58 |         Location.new(raise_matches[1], raise_matches[2].to_i)
59 |       end
60 |     end
61 |   end
62 | end
63 | 
--------------------------------------------------------------------------------
/lib/tldr/value/wip_test.rb:
--------------------------------------------------------------------------------
 1 | class TLDR
 2 |   WIPTest = Struct.new(:test, :start_time, :thread) do
 3 |     attr_reader :backtrace_at_exit
 4 | 
 5 |     def capture_backtrace_at_exit
 6 |       @backtrace_at_exit = thread&.backtrace
 7 |     end
 8 |   end
 9 | end
10 | 
--------------------------------------------------------------------------------
/lib/tldr/version.rb:
--------------------------------------------------------------------------------
1 | class TLDR
2 |   VERSION = "1.1.1"
3 | end
4 | 
--------------------------------------------------------------------------------
/lib/tldr/watcher.rb:
--------------------------------------------------------------------------------
 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 |       print <<~MSG.chomp
 9 |         Waiting for changes in --load-path directories: #{config.load_paths.map(&:inspect).join(", ")}
10 | 
11 |         When a file changes, TLDR will run this command:
12 | 
13 |         $ #{tldr_command}
14 | 
15 |         Watching...
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
36 | 
--------------------------------------------------------------------------------
/lib/tldr/yaml_parser.rb:
--------------------------------------------------------------------------------
 1 | require "optparse"
 2 | 
 3 | class TLDR
 4 |   class YamlParser
 5 |     def parse path
 6 |       require "yaml"
 7 |       (YAML.load_file(path) || {})
 8 |         .transform_keys { |k| k.to_sym }
 9 |         .tap do |dotfile_args|
10 |           # Since we don't have shell expansion, we have to glob any paths ourselves
11 |           if dotfile_args.key?(:paths)
12 |             dotfile_args[:paths] = dotfile_args[:paths].flat_map { |path| Dir[path] }
13 |           end
14 |           if dotfile_args.key?(:timeout)
15 |             dotfile_args[:timeout] = case dotfile_args[:timeout]
16 |             when true then Config::DEFAULT_TIMEOUT
17 |             when false then -1
18 |             when String then Float(dotfile_args[:timeout])
19 |             else dotfile_args[:timeout]
20 |             end
21 |           end
22 | 
23 |           if (invalid_args = dotfile_args.except(*CONFIG_ATTRIBUTES)).any?
24 |             raise Error, "Invalid keys in #{File.basename(path)} file: #{invalid_args.keys.join(", ")}"
25 |           end
26 |         end
27 |     end
28 |   end
29 | end
30 | 
--------------------------------------------------------------------------------
/script/setup:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env bash
 2 | 
 3 | set -e
 4 | 
 5 | bundle
 6 | 
 7 | cd example/a
 8 | bundle
 9 | cd ../..
10 | 
11 | cd example/b
12 | bundle
13 | cd ../..
14 | 
15 | cd example/c
16 | bundle
17 | cd ../..
18 | 
19 | cd example/d
20 | bundle
21 | cd ../..
22 | 
--------------------------------------------------------------------------------
/script/test:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env bash
 2 | 
 3 | set -e
 4 | 
 5 | bundle exec rake
 6 | 
 7 | cd example/a
 8 | bundle exec tldr | ruby -e 'exit(ARGF.read =~ /\n..\n/ ? 0 : 1)'
 9 | cd ../..
10 | 
11 | cd example/b
12 | bundle exec ruby -Itest test/some_test.rb | ruby -e 'exit(ARGF.read =~ /\n.\n/ ? 0 : 1)'
13 | bundle exec rake tldr | ruby -e 'exit(ARGF.read =~ /\n.\n/ ? 0 : 1)'
14 | cd ../..
15 | 
--------------------------------------------------------------------------------
/script/upgrade:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env bash
 2 | 
 3 | set -e
 4 | 
 5 | bundle update
 6 | 
 7 | cd example/a
 8 | bundle update
 9 | cd ../..
10 | 
11 | cd example/b
12 | bundle update
13 | cd ../..
14 | 
15 | cd example/c
16 | bundle update
17 | cd ../..
18 | 
19 | cd example/d
20 | bundle update
21 | cd ../..
22 | 
--------------------------------------------------------------------------------
/tests/api_driver_test.rb:
--------------------------------------------------------------------------------
 1 | require_relative "test_helper"
 2 | 
 3 | class ApiDriverTest < Minitest::Test
 4 |   def test_run_method
 5 |     result = TLDRunner.run_command("bundle exec ruby tests/driver/api_driver.rb")
 6 | 
 7 |     assert_includes result.stdout, <<~MSG
 8 |       Command: bundle exec tldr --seed 1 "tests/fixture/c.rb"
 9 |       --seed 1
10 | 
11 |       Running:
12 | 
13 |       C1
14 |       .C3
15 |       .C2
16 |       .
17 |     MSG
18 |   end
19 | end
20 | 
--------------------------------------------------------------------------------
/tests/at_exit_driver_test.rb:
--------------------------------------------------------------------------------
 1 | require_relative "test_helper"
 2 | 
 3 | class AtExitDriverTest < Minitest::Test
 4 |   def test_running_at_exit
 5 |     result = TLDRunner.run_command("bundle exec ruby tests/driver/at_exit_driver.rb")
 6 | 
 7 |     assert_includes result.stdout, <<~MSG
 8 |       Command: bundle exec tldr --seed 5 --exclude-name "test_y"
 9 |       --seed 5
10 | 
11 |       Running:
12 | 
13 |       X
14 |       .Z
15 |       .
16 |     MSG
17 |   end
18 | 
19 |   def test_running_cli_when_at_exit_is_also_there_only_runs_once
20 |     result = TLDRunner.run_command("bundle exec tldr tests/driver/at_exit_driver.rb --exclude-name test_x --seed 1")
21 | 
22 |     # tldr command wins
23 |     assert_equal result.stdout.scan("Command: bundle exec tldr").size, 1
24 |     assert_includes result.stdout, <<~MSG
25 |       Running:
26 | 
27 |       Y
28 |       .Z
29 |       .
30 |     MSG
31 |   end
32 | end
33 | 
--------------------------------------------------------------------------------
/tests/autorun_test.rb:
--------------------------------------------------------------------------------
 1 | require_relative "test_helper"
 2 | 
 3 | class AutorunTest < Minitest::Test
 4 |   def test_running_at_exit
 5 |     result = TLDRunner.run_command <<~CMD
 6 |       bundle exec ruby tests/fixture/autorun.rb --seed 42 --exclude-name "test_orange" --no-prepend
 7 |     CMD
 8 | 
 9 |     assert_includes result.stdout, <<~MSG.chomp
10 |       Command: bundle exec tldr --seed 42 --exclude-name "test_orange" --no-prepend
11 |       --seed 42
12 | 
13 |       Running:
14 | 
15 |       ..
16 | 
17 |       Finished
18 |     MSG
19 |   end
20 | end
21 | 
--------------------------------------------------------------------------------
/tests/backtrace_filter_test.rb:
--------------------------------------------------------------------------------
 1 | require_relative "test_helper"
 2 | 
 3 | class BacktraceFilterTest < Minitest::Test
 4 |   def setup
 5 |     @subject = TLDR::BacktraceFilter.new
 6 |   end
 7 | 
 8 |   def test_no_matches_just_dupes
 9 |     ar = ["foo", "bar", "baz"]
10 | 
11 |     assert_equal ar, @subject.filter(ar)
12 |     refute_same ar, @subject.filter(ar)
13 |   end
14 | 
15 |   def test_takes_leading_meaningful_frames_until_it_hits_internal_ones
16 |     ar = [
17 |       "/foo/bar/baz.rb:1:in `foo'",
18 |       "/foo/bar/baz.rb:8:in `baz'",
19 |       "#{TLDR::BacktraceFilter::BASE_PATH}/foo.rb:2:in `foo'",
20 |       "/foo/bar/baz.rb:5:in `bar'"
21 |     ]
22 | 
23 |     assert_equal ar[0..1], @subject.filter(ar)
24 |   end
25 | 
26 |   def test_selects_meaningful_frames_if_starts_with_internal_sorbet_and_concurrent_ruby_ones
27 |     ar = [
28 |       "#{TLDR::BacktraceFilter::BASE_PATH}/foo.rb:1:in `foo'",
29 |       "/foo/bar/baz.rb:1:in `foo'",
30 |       "#{TLDR::BacktraceFilter::BASE_PATH}/foo.rb:2:in `foo'",
31 |       "/foo/bar/baz.rb:5:in `bar'"
32 |     ]
33 | 
34 |     assert_equal [ar[1], ar[3]], @subject.filter(ar)
35 |   end
36 | end
37 | 
--------------------------------------------------------------------------------
/tests/base_path_test.rb:
--------------------------------------------------------------------------------
 1 | require_relative "test_helper"
 2 | 
 3 | class BasePathTest < Minitest::Test
 4 |   def test_configuring_base_path
 5 |     result = TLDRunner.run_command("bundle exec tldr --seed 1 --no-prepend --base-path example/a")
 6 | 
 7 |     assert_empty result.stderr
 8 |     assert_includes result.stdout, <<~MSG
 9 |       Command: bundle exec tldr --seed 1 --no-prepend --base-path "example/a"
10 |       --seed 1
11 | 
12 |       Running:
13 | 
14 |       ..
15 |     MSG
16 |   end
17 | end
18 | 
--------------------------------------------------------------------------------
/tests/custom_reporter_test.rb:
--------------------------------------------------------------------------------
 1 | require_relative "test_helper"
 2 | 
 3 | class CustomReporterTest < Minitest::Test
 4 |   def test_success
 5 |     result = TLDRunner.should_succeed "success.rb", <<~OPTIONS
 6 |       --helper tests/fixture/custom_reporter_helper.rb --reporter "SuitReporter"
 7 |     OPTIONS
 8 | 
 9 |     assert_empty result.stderr
10 |     assert_equal "♠︎", result.stdout
11 |   end
12 | 
13 |   def test_suite_summary
14 |     result = TLDRunner.should_fail "suite_summary.rb", <<~OPTIONS
15 |       --helper tests/fixture/custom_reporter_helper.rb --reporter "SuitReporter" --seed 22
16 |     OPTIONS
17 | 
18 |     assert_empty result.stderr
19 |     assert_equal "♣︎♦︎♥︎♦︎♠︎♥︎♠︎♣︎", result.stdout
20 |   end
21 | 
22 |   def test_unknown_reporter
23 |     result = TLDRunner.should_fail "suite_summary.rb", <<~OPTIONS
24 |       --helper tests/fixture/custom_reporter_helper.rb --reporter "PantsReporter"
25 |     OPTIONS
26 | 
27 |     assert_includes result.stderr, <<~MSG.chomp
28 |       Unknown reporter 'PantsReporter' (are you sure it was loaded by your test or helper?)
29 |     MSG
30 |     assert_empty result.stdout
31 |   end
32 | 
33 |   def test_no_hooks_defined
34 |     result = TLDRunner.should_succeed "success.rb", <<~OPTIONS
35 |       --helper tests/fixture/custom_reporter_helper.rb --reporter "HooklessReporter"
36 |     OPTIONS
37 | 
38 |     assert_empty result.stderr
39 |     assert_empty result.stdout
40 |   end
41 | 
42 |   def test_invalid_reporter
43 |     result = TLDRunner.should_fail "suite_summary.rb", <<~OPTIONS
44 |       --helper tests/fixture/custom_reporter_helper.rb --reporter "InvalidReporter"
45 |     OPTIONS
46 | 
47 |     assert_includes result.stderr, <<~MSG.chomp
48 |       Reporter 'InvalidReporter' expected to be a class, but was a Module
49 |     MSG
50 |     assert_empty result.stdout
51 |   end
52 | end
53 | 
--------------------------------------------------------------------------------
/tests/directory_paths_test.rb:
--------------------------------------------------------------------------------
 1 | require_relative "test_helper"
 2 | 
 3 | class DirectoryPathsTest < Minitest::Test
 4 |   def test_directories_work
 5 |     result = TLDRunner.should_succeed "folder"
 6 | 
 7 |     assert_includes result.stdout, "A1"
 8 |     assert_includes result.stdout, "B1"
 9 |   end
10 | end
11 | 
--------------------------------------------------------------------------------
/tests/dont_run_these_in_parallel_test.rb:
--------------------------------------------------------------------------------
 1 | require_relative "test_helper"
 2 | 
 3 | class DontRunTheseInParallelTest < Minitest::Test
 4 |   def test_not_running_in_parallel
 5 |     result = TLDRunner.should_succeed "dont_run_these_in_parallel.rb"
 6 | 
 7 |     assert_includes result.stdout, "6 test methods"
 8 |   end
 9 | 
10 |   def test_prepend_pushes_matching_tests_to_the_front
11 |     result = TLDRunner.should_succeed "dont_run_these_in_parallel.rb", "-v --prepend tests/fixture/dont_run_these_in_parallel.rb:35"
12 | 
13 |     [
14 |       "TA#test_1",
15 |       "TB#test_1",
16 |       "TB#test_2",
17 |       "TC#test_1",
18 |       "TC#test_2"
19 |     ].each do |other|
20 |       assert_strings_appear_in_this_order result.stdout, ["TA#test_2", other]
21 |     end
22 |   end
23 | 
24 |   def test_not_running_parallel_specified_by_superclass
25 |     result = TLDRunner.should_succeed "dont_run_these_in_parallel_superclasses.rb"
26 | 
27 |     assert_includes result.stdout, "2 test methods"
28 |   end
29 | end
30 | 
--------------------------------------------------------------------------------
/tests/dotfile_test.rb:
--------------------------------------------------------------------------------
 1 | require_relative "test_helper"
 2 | 
 3 | class DotfileTest < Minitest::Test
 4 |   def test_a_dotfile
 5 |     result = TLDRunner.run_command("BUNDLE_GEMFILE=\"example/c/Gemfile\" bundle exec tldr --seed 1 --no-prepend --base-path example/c")
 6 | 
 7 |     assert_empty result.stderr
 8 |     assert_includes result.stdout, <<~MSG
 9 |       👓
10 |       Command: bundle exec tldr --seed 1 --helper "spec/spec_helper.rb" --no-prepend --base-path "example/c" "spec/math_spec.rb"
11 |       --seed 1
12 | 
13 |       Running:
14 | 
15 |       .
16 |     MSG
17 |   end
18 | 
19 |   def test_no_config_path_doesnt_load_those_settings
20 |     result = TLDRunner.run_command("bundle exec tldr --seed 1 --no-prepend --base-path example/c --no-config")
21 | 
22 |     assert_empty result.stderr
23 |     assert_includes result.stdout, <<~MSG
24 |       Command: bundle exec tldr --seed 1 --no-prepend --base-path "example/c" --no-config
25 |     MSG
26 |     assert_includes result.stdout, "0 test methods"
27 |   end
28 | 
29 |   def test_a_lot_of_values_in_a_dotfile
30 |     result = TLDRunner.run_command("bundle exec tldr --base-path example/d")
31 | 
32 |     refute result.success?
33 |     assert_includes result.stdout, <<~MSG
34 |       Command: bundle exec tldr --fail-fast --parallel --seed 42 --name "/test_*/" --name "test_it" --exclude-name "test_b_1" --exclude-path "c.rb:4" --helper "test_helper.rb" --prepend "a.rb:3" --load-path "app" --load-path "lib" --base-path "example/d" --verbose "b.rb"
35 |     MSG
36 |     assert_includes result.stderr, <<~MSG
37 |       1) BTest#test_b_2 [b.rb:7] errored:
38 |       wups
39 | 
40 |         Re-run this test:
41 |           bundle exec tldr --base-path "example/d" "b.rb:6"
42 |     MSG
43 |   end
44 | 
45 |   def test_overriding_a_lot_of_values_in_a_dotfile
46 |     result = TLDRunner.run_command("bundle exec tldr --base-path example/d --seed 5 --load-path foo --no-parallel --name test_stuff --prepend nope --exclude-path nada --exclude-name test_b_2")
47 | 
48 |     assert result.success?
49 |     assert_includes result.stdout, <<~MSG
50 |       Command: bundle exec tldr --fail-fast --seed 5 --name "test_stuff" --exclude-name "test_b_2" --exclude-path "nada" --helper "test_helper.rb" --prepend "nope" --load-path "foo" --base-path "example/d" --verbose "b.rb"
51 |     MSG
52 |   end
53 | 
54 |   def test_a_custom_dotfile_path
55 |     result = TLDRunner.run_command("BUNDLE_GEMFILE=\"example/a/Gemfile\" bundle exec tldr --seed 1 --base-path example/a --config config/TldrFile")
56 | 
57 |     assert_empty result.stderr
58 |     assert_includes result.stdout, <<~MSG
59 |       Command: bundle exec tldr --seed 1 --base-path "example/a" --config config/TldrFile "test/test_subtract.rb"
60 |     MSG
61 |   end
62 | end
63 | 
--------------------------------------------------------------------------------
/tests/driver/api_driver.rb:
--------------------------------------------------------------------------------
1 | require "tldr"
2 | 
3 | TLDR::Run.tests(TLDR::Config.new(paths: ["tests/fixture/c.rb"], seed: 1))
4 | 
--------------------------------------------------------------------------------
/tests/driver/at_exit_driver.rb:
--------------------------------------------------------------------------------
 1 | require "tldr"
 2 | 
 3 | TLDR::Run.at_exit!(TLDR::Config.new(seed: 5, exclude_names: ["test_y"]))
 4 | 
 5 | # First in wins
 6 | TLDR::Run.at_exit!(TLDR::Config.new(seed: 5, exclude_names: ["test_z"]))
 7 | 
 8 | class Z < TLDR
 9 |   def test_z
10 |     puts "Z"
11 |   end
12 | end
13 | 
14 | class Y < TLDR
15 |   def test_y
16 |     puts "Y"
17 |   end
18 | end
19 | 
20 | class X < TLDR
21 |   def test_x
22 |     puts "X"
23 |   end
24 | end
25 | 
--------------------------------------------------------------------------------
/tests/emoji_test.rb:
--------------------------------------------------------------------------------
 1 | require_relative "test_helper"
 2 | 
 3 | class EmojiTest < Minitest::Test
 4 |   def test_emoji_disabled
 5 |     result = TLDRunner.should_fail "emoji.rb", "--seed 1 --no-emoji"
 6 | 
 7 |     refute_includes result.stdout, "🏃"
 8 |     assert_includes result.stdout, <<~MSG
 9 |       S.EF
10 |     MSG
11 |   end
12 | 
13 |   def test_emoji_enabled
14 |     result = TLDRunner.should_fail "emoji.rb", "--seed 1 --emoji"
15 | 
16 |     assert_includes result.stdout, <<~MSG
17 |       Command: bundle exec tldr --seed 1 --emoji "tests/fixture/emoji.rb"
18 |       🌱 --seed 1
19 | 
20 |       🏃 Running:
21 | 
22 |       🫥😁🤬😡
23 | 
24 |       Skipped tests:
25 | 
26 |         - Emoji#test_skip [tests/fixture/emoji.rb:13]
27 |     MSG
28 |   end
29 | end
30 | 
--------------------------------------------------------------------------------
/tests/exclude_names_test.rb:
--------------------------------------------------------------------------------
 1 | require_relative "test_helper"
 2 | 
 3 | class ExcludePathTest < Minitest::Test
 4 |   def test_a_simple_exclude_name
 5 |     result = TLDRunner.should_succeed "folder", "--exclude-name test_b_1"
 6 | 
 7 |     assert_includes_all result.stdout, ["A1", "A2", "A3", "B2", "B3"]
 8 |     refute_includes result.stdout, "B1"
 9 |   end
10 | 
11 |   def test_three_names
12 |     result = TLDRunner.should_succeed "folder", "--exclude-name test_b_1,test_a_2 --exclude-name test_b_3"
13 | 
14 |     assert_includes_all result.stdout, ["A1", "A3", "B2"]
15 |     assert_includes_none result.stdout, ["B1", "A2", "B3"]
16 |   end
17 | 
18 |   def test_a_pattern_with_commas
19 |     result = TLDRunner.should_succeed "folder", "--exclude-name \"/test_(a|b)_[23]{1,2}/\""
20 | 
21 |     assert_includes_all result.stdout, ["A1", "B1"]
22 |     assert_includes_none result.stdout, ["A2", "B2", "A3", "B3"]
23 |   end
24 | end
25 | 
--------------------------------------------------------------------------------
/tests/exclude_path_test.rb:
--------------------------------------------------------------------------------
 1 | require_relative "test_helper"
 2 | 
 3 | class ExcludePathTest < Minitest::Test
 4 |   def test_a_simple_exclude_path
 5 |     result = TLDRunner.should_succeed "folder", "--exclude-path tests/fixture/folder/b.rb:3"
 6 | 
 7 |     assert_includes_all result.stdout, ["A1", "A2", "A3", "B2", "B3"]
 8 |     refute_includes result.stdout, "B1"
 9 |   end
10 | 
11 |   def test_a_glob
12 |     result = TLDRunner.should_succeed "**", "--exclude-path \"tests/fixture/**\""
13 | 
14 |     assert_includes result.stdout, "0 test methods"
15 |   end
16 | 
17 |   def test_errors_on_glob_plus_line_number
18 |     result = TLDRunner.should_fail "folder", "--exclude-path \"tests/fixture/folder/*.rb:4\""
19 | 
20 |     assert_includes result.stderr, "Can't combine globs and line numbers in: tests/fixture/folder/*.rb:4 (TLDR::Error)"
21 |   end
22 | end
23 | 
--------------------------------------------------------------------------------
/tests/exit_code_test.rb:
--------------------------------------------------------------------------------
  1 | require_relative "test_helper"
  2 | 
  3 | class ExitCodeTest < Minitest::Test
  4 |   def test_success
  5 |     result = TLDRunner.should_succeed "success.rb"
  6 | 
  7 |     assert_equal "", result.stderr
  8 |     assert_equal 0, result.exit_code
  9 |     assert_includes result.stdout, "\n.\n"
 10 |     # Command shouldn't include --seed if it wasn't explicitly set
 11 |     assert_includes result.stdout, "Command: bundle exec tldr \"tests/fixture/success.rb\"\n"
 12 |     assert_match(/--seed \d+/, result.stdout)
 13 |   end
 14 | 
 15 |   def test_failure
 16 |     result = TLDRunner.should_fail "fail.rb"
 17 | 
 18 |     assert_includes result.stdout, "F"
 19 |     assert_includes result.stderr, <<~MSG.chomp
 20 |       Failing tests:
 21 | 
 22 |       1) FailTest#test_fails [tests/fixture/fail.rb:3] failed:
 23 |       Expected false to be truthy
 24 | 
 25 |         Re-run this test:
 26 |           bundle exec tldr "tests/fixture/fail.rb:2"
 27 |     MSG
 28 |     assert_equal 1, result.exit_code
 29 |   end
 30 | 
 31 |   def test_error
 32 |     result = TLDRunner.should_fail "error.rb"
 33 | 
 34 |     assert_includes result.stdout, "E"
 35 |     assert_includes result.stderr, <<~MSG.chomp
 36 |       Failing tests:
 37 | 
 38 |       1) ErrorTest#test_errors [tests/fixture/error.rb:3] errored:
 39 |       💥
 40 | 
 41 |         Re-run this test:
 42 |           bundle exec tldr "tests/fixture/error.rb:2"
 43 |     MSG
 44 |     assert_equal 2, result.exit_code
 45 |   end
 46 | 
 47 |   def test_skip
 48 |     result = TLDRunner.should_succeed "skip.rb"
 49 | 
 50 |     assert_includes result.stdout, <<~MSG
 51 |       Skipped tests:
 52 | 
 53 |         - SuccessTest#test_skips [tests/fixture/skip.rb:2]
 54 |     MSG
 55 |     assert_equal 0, result.exit_code
 56 |     assert_includes result.stdout, "S"
 57 |   end
 58 | 
 59 |   def test_timeout_with_exit_0_on_timeout_flag
 60 |     result = TLDRunner.run "slow.rb", "--no-parallel --timeout 0.001 --exit-0-on-timeout"
 61 | 
 62 |     assert_equal 0, result.exit_code
 63 |     assert_includes result.stderr, "too long; didn't run!"
 64 |     assert_includes result.stderr, "ABORTED RUN"
 65 |   end
 66 | 
 67 |   def test_timeout_without_exit_0_on_timeout_flag
 68 |     result = TLDRunner.run "slow.rb", "--no-parallel --timeout 0.001"
 69 | 
 70 |     assert_equal 3, result.exit_code
 71 |     assert_includes result.stderr, "too long; didn't run!"
 72 |     assert_includes result.stderr, "ABORTED RUN"
 73 |   end
 74 | 
 75 |   def test_timeout_with_failing_test_and_exit_0_on_timeout_flag
 76 |     result = TLDRunner.run ["fail.rb", "slow.rb"], "--timeout 0.01 --exit-0-on-timeout"
 77 | 
 78 |     # Even with --exit-0-on-timeout, test failures/errors should result in proper exit code
 79 |     assert_equal 1, result.exit_code  # 1 for failures
 80 |     assert_includes result.stderr, "ABORTED RUN"
 81 |     # Should have run at least one failing test before timing out
 82 |     assert_includes result.stderr, "Failing tests:"
 83 |   end
 84 | 
 85 |   def test_failure_with_exit_2_on_failure_flag
 86 |     result = TLDRunner.run "fail.rb", "--exit-2-on-failure"
 87 | 
 88 |     # With --exit-2-on-failure, failures should exit with code 2
 89 |     assert_equal 2, result.exit_code
 90 |     assert_includes result.stderr, "Failing tests:"
 91 |   end
 92 | 
 93 |   def test_timeout_with_failing_test_and_exit_2_on_failure_flag
 94 |     result = TLDRunner.run ["fail.rb", "slow.rb"], "--timeout 0.01 --exit-2-on-failure"
 95 | 
 96 |     # With --exit-2-on-failure, failures during timeout should exit with code 2
 97 |     assert_equal 2, result.exit_code
 98 |     assert_includes result.stderr, "ABORTED RUN"
 99 |     assert_includes result.stderr, "Failing tests:"
100 |   end
101 | end
102 | 
--------------------------------------------------------------------------------
/tests/fail_fast_test.rb:
--------------------------------------------------------------------------------
 1 | require_relative "test_helper"
 2 | 
 3 | class FailFastTest < Minitest::Test
 4 |   def test_fails_fast
 5 |     result = TLDRunner.should_fail "fail_fast.rb", "--seed 9 --fail-fast --emoji"
 6 | 
 7 |     assert_includes result.stdout, "😁"
 8 |     assert_includes result.stdout, "😡"
 9 |     assert_includes result.stderr, "Failing fast after FailFast#test_fail [tests/fixture/fail_fast.rb:4] failed."
10 |     assert_includes result.stderr, "1 test was not run at all."
11 | 
12 |     # Disabled this assertion b/c we no longer have a black-box way to starve worker threads so as to force this easily:
13 |     # assert_includes result.stderr, "1 test was cancelled in progress."
14 |   end
15 | end
16 | 
--------------------------------------------------------------------------------
/tests/fixture/autorun.rb:
--------------------------------------------------------------------------------
 1 | require "tldr/autorun"
 2 | 
 3 | class FruitTest < TLDR
 4 |   FRUIT = %w[🍌 🍎 🍊 🍈].freeze
 5 | 
 6 |   def test_banana
 7 |     assert_includes FRUIT, "🍌"
 8 |   end
 9 | 
10 |   def test_orange
11 |     assert_includes FRUIT, "🍊"
12 |   end
13 | 
14 |   def test_peach
15 |     refute_includes FRUIT, "🍑"
16 |   end
17 | end
18 | 
--------------------------------------------------------------------------------
/tests/fixture/c.rb:
--------------------------------------------------------------------------------
 1 | class C < TLDR
 2 |   def test_c_1
 3 |     puts "C1"
 4 |   end
 5 | 
 6 |   def test_c_2
 7 |     puts "C2"
 8 |   end
 9 | 
10 |   def test_c_3
11 |     puts "C3"
12 |   end
13 | end
14 | 
--------------------------------------------------------------------------------
/tests/fixture/custom_reporter.rb:
--------------------------------------------------------------------------------
 1 | class SuitReporter
 2 |   def before_suite tests
 3 |   end
 4 | 
 5 |   def after_test test_result
 6 |     print case test_result.type
 7 |     when :success then "♠︎"
 8 |     when :skip then "♣︎"
 9 |     when :failure then "♥︎"
10 |     when :error then "♦︎"
11 |     end
12 |   end
13 | 
14 |   def after_suite test_results
15 |   end
16 | 
17 |   def after_tldr planned_tests, wip_tests, test_results
18 |   end
19 | 
20 |   def after_fail_fast planned_tests, wip_tests, test_results, last_result
21 |   end
22 | end
23 | 
--------------------------------------------------------------------------------
/tests/fixture/custom_reporter_helper.rb:
--------------------------------------------------------------------------------
1 | require_relative "custom_reporter"
2 | 
3 | module InvalidReporter
4 | end
5 | 
6 | class HooklessReporter
7 | end
8 | 
--------------------------------------------------------------------------------
/tests/fixture/diffs.rb:
--------------------------------------------------------------------------------
 1 | class Diffs < TLDR
 2 |   def test_a_big_hash
 3 |     expected = {
 4 |       "a" => 1,
 5 |       :b => 2,
 6 |       :c => <<~MSG,
 7 |         Here
 8 |         is
 9 |         some
10 |         long
11 | 
12 |         text!
13 |       MSG
14 |       :d => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
15 |     }
16 | 
17 |     actual = {
18 |       "a" => 1,
19 |       :b => 3,
20 |       :c => <<~MSG,
21 |         Here
22 |         is
23 |         some
24 |         LONG
25 | 
26 |         text!
27 |       MSG
28 |       :d => [1, 2, 3, 4, 7, 6, 7, 8, 9, 10]
29 |     }
30 | 
31 |     assert_equal expected, actual
32 |   end
33 | end
34 | 
--------------------------------------------------------------------------------
/tests/fixture/dont_run_these_in_parallel.rb:
--------------------------------------------------------------------------------
 1 | require "time"
 2 | 
 3 | # Clock is a global resource like Time, but can be set and reset by any test
 4 | class Clock
 5 |   @@time = nil
 6 |   def self.now
 7 |     @@time || Time.now
 8 |   end
 9 | 
10 |   def self.change! time
11 |     @@time = time
12 |     if block_given?
13 |       yield
14 |       reset!
15 |     end
16 |   end
17 | 
18 |   def self.reset!
19 |     @@time = nil
20 |   end
21 | end
22 | 
23 | class TA < TLDR
24 |   dont_run_these_in_parallel!
25 | 
26 |   def test_1
27 |     Clock.change!(Time.parse("2080-01-01 12:00:00 UTC")) do
28 |       sleep 0.01
29 |       assert_equal 2080, Clock.now.year
30 |     end
31 |   end
32 | 
33 |   def test_2
34 |     Clock.change!(Time.parse("2070-01-01 12:00:00 UTC")) do
35 |       sleep 0.01
36 |       assert_equal 2070, Clock.now.year
37 |     end
38 |   end
39 | end
40 | 
41 | class TB < TLDR
42 |   # dont_run_these_in_parallel! can specify any test identifiers as
43 |   # (class, method) tuples, not just in one's own class
44 |   dont_run_these_in_parallel! [
45 |     [TB, :test_1],
46 |     ["TC", :test_2]
47 |   ]
48 | 
49 |   def test_1
50 |     Clock.change!(Time.parse("2060-01-01 12:00:00 UTC")) do
51 |       sleep 0.01
52 |       assert_equal 2060, Clock.now.year
53 |     end
54 |   end
55 | 
56 |   def test_2
57 |     assert_equal Time.now.year, Clock.now.year
58 |   end
59 | end
60 | 
61 | class TC < TLDR
62 |   def test_1
63 |     assert_equal Time.now.hour, Clock.now.hour
64 |   end
65 | 
66 |   def test_2
67 |     Clock.change!(Time.parse("2050-01-01 12:00:00 UTC")) do
68 |       sleep 0.01
69 |       assert_equal 2050, Clock.now.year
70 |     end
71 |   end
72 | end
73 | 
--------------------------------------------------------------------------------
/tests/fixture/dont_run_these_in_parallel_superclasses.rb:
--------------------------------------------------------------------------------
 1 | require "time"
 2 | 
 3 | # Clock is a global resource like Time, but can be set and reset by any test
 4 | class Clock
 5 |   @@time = nil
 6 |   def self.now
 7 |     @@time || Time.now
 8 |   end
 9 | 
10 |   def self.change! time
11 |     @@time = time
12 |     if block_given?
13 |       yield
14 |       reset!
15 |     end
16 |   end
17 | 
18 |   def self.reset!
19 |     @@time = nil
20 |   end
21 | end
22 | 
23 | class Super < TLDR
24 |   dont_run_these_in_parallel!
25 | end
26 | 
27 | class SubTA < Super
28 |   def test_1
29 |     Clock.change!(Time.parse("2080-01-01 12:00:00 UTC")) do
30 |       sleep 0.01
31 |       assert_equal 2080, Clock.now.year
32 |     end
33 |   end
34 | end
35 | 
36 | class SubTB < Super
37 |   def test_1
38 |     Clock.change!(Time.parse("2070-01-01 12:00:00 UTC")) do
39 |       sleep 0.01
40 |       assert_equal 2070, Clock.now.year
41 |     end
42 |   end
43 | end
44 | 
--------------------------------------------------------------------------------
/tests/fixture/emoji.rb:
--------------------------------------------------------------------------------
 1 | class Emoji < TLDR
 2 |   def test_pass
 3 |   end
 4 | 
 5 |   def test_fail
 6 |     assert false
 7 |   end
 8 | 
 9 |   def test_error
10 |     raise
11 |   end
12 | 
13 |   def test_skip
14 |     skip
15 |   end
16 | end
17 | 
--------------------------------------------------------------------------------
/tests/fixture/error.rb:
--------------------------------------------------------------------------------
1 | class ErrorTest < TLDR
2 |   def test_errors
3 |     raise "💥"
4 |   end
5 | end
6 | 
--------------------------------------------------------------------------------
/tests/fixture/fail.rb:
--------------------------------------------------------------------------------
1 | class FailTest < TLDR
2 |   def test_fails
3 |     assert false
4 |   end
5 | end
6 | 
--------------------------------------------------------------------------------
/tests/fixture/fail_fast.rb:
--------------------------------------------------------------------------------
 1 | class FailFast < TLDR
 2 |   def test_fail
 3 |     sleep 0.1
 4 |     assert false
 5 |   end
 6 | 
 7 |   def test_pass_already_run
 8 |     sleep 0.05
 9 |   end
10 | 
11 |   def test_pass_wont_finish
12 |     sleep 0.2
13 |   end
14 | 
15 |   def test_pass_wont_even_run
16 |   end
17 | end
18 | 
--------------------------------------------------------------------------------
/tests/fixture/folder/a.rb:
--------------------------------------------------------------------------------
 1 | class A < TLDR
 2 |   def test_a_1
 3 |     puts "A1"
 4 |   end
 5 | 
 6 |   def test_a_2
 7 |     puts "A2"
 8 |   end
 9 | 
10 |   def test_a_3
11 |     puts "A3"
12 |   end
13 | end
14 | 
--------------------------------------------------------------------------------
/tests/fixture/folder/b.rb:
--------------------------------------------------------------------------------
 1 | class B < TLDR
 2 |   def test_b_1
 3 |     puts "B1"
 4 |   end
 5 | 
 6 |   def test_b_2
 7 |     puts "B2"
 8 |   end
 9 | 
10 |   def test_b_3
11 |     puts "B3"
12 |   end
13 | end
14 | 
--------------------------------------------------------------------------------
/tests/fixture/helper_a.rb:
--------------------------------------------------------------------------------
1 | puts "Helper A"
2 | 
--------------------------------------------------------------------------------
/tests/fixture/helper_b.rb:
--------------------------------------------------------------------------------
1 | puts "Helper B"
2 | 
--------------------------------------------------------------------------------
/tests/fixture/hooks.rb:
--------------------------------------------------------------------------------
 1 | class Hooks < TLDR
 2 |   def setup
 3 |     super
 4 |     print "\nA"
 5 |   end
 6 | 
 7 |   def around &test
 8 |     print "("
 9 |     test.call
10 |     print ")"
11 |   end
12 | 
13 |   def test_1
14 |     print "B"
15 |   end
16 | 
17 |   def test_2
18 |     print "B"
19 |   end
20 | 
21 |   def teardown
22 |     super
23 |     print "C"
24 |   end
25 | end
26 | 
27 | class BadHook < TLDR
28 |   def around &test
29 |     print "("
30 |     # wups, didn't call test.call!
31 |     print ")"
32 |   end
33 | 
34 |   def test_3
35 |     print "🎯"
36 |   end
37 | end
38 | 
--------------------------------------------------------------------------------
/tests/fixture/line_number.rb:
--------------------------------------------------------------------------------
 1 | class LineNumber < TLDR
 2 |   def test_some_line
 3 |     assert true
 4 |   end
 5 | 
 6 |   def test_some_other_line
 7 |     skip
 8 |   end
 9 | 
10 |   def test_some_third_line
11 |     assert false
12 |   end
13 | end
14 | 
--------------------------------------------------------------------------------
/tests/fixture/name_filters.rb:
--------------------------------------------------------------------------------
 1 | class NameFilters < TLDR
 2 |   def test_my_first_case
 3 |     puts "1️⃣"
 4 |   end
 5 | 
 6 |   def test_my_second_case
 7 |     puts "2️⃣"
 8 |   end
 9 | 
10 |   def test_a_third_case
11 |     puts "3️⃣"
12 |   end
13 | end
14 | 
--------------------------------------------------------------------------------
/tests/fixture/parallel.rb:
--------------------------------------------------------------------------------
 1 | class Parallel < TLDR
 2 |   # run manually with:
 3 |   #  $ time be tldr tests/fixture/parallel.rb
 4 |   # TODO - think of a clever way to assert runtime without just making the test suite slow in the precess
 5 |   (Concurrent.processor_count * 4).times do |i|
 6 |     define_method :"test_#{i}" do
 7 |       sleep rand 0.2..0.8
 8 |       if i % 4 == 0
 9 |         assert false, "failing every fourth test and this is #{i}"
10 |       end
11 |     end
12 |   end
13 | end
14 | 
--------------------------------------------------------------------------------
/tests/fixture/run_these_together.rb:
--------------------------------------------------------------------------------
 1 | class TA < TLDR
 2 |   run_these_together!
 3 | 
 4 |   @@nonsense = 0
 5 | 
 6 |   def teardown
 7 |     @@nonsense = 0
 8 |   end
 9 | 
10 |   def test_1
11 |     @@nonsense += 1
12 |     sleep 0.1
13 |     assert_equal 1, @@nonsense
14 |   end
15 | 
16 |   def test_2
17 |     @@nonsense += 1
18 |     sleep 0.1
19 |     assert_equal 1, @@nonsense
20 |   end
21 | end
22 | 
23 | class TB < TLDR
24 |   # The setup hook in TB will implicate both tests, but TC's dependency on
25 |   # TB.woah is isolated to TC#test_2
26 |   run_these_together! [
27 |     [TB, nil],
28 |     ["TC", :test_2]
29 |   ]
30 | 
31 |   @@woah = 0
32 |   def self.woah
33 |     @@woah
34 |   end
35 | 
36 |   def setup
37 |     TB.woah = 0
38 |   end
39 | 
40 |   def self.woah= woah
41 |     @@woah = woah
42 |   end
43 | 
44 |   def test_1
45 |     TB.woah += 1
46 |     sleep 0.1
47 |     assert_equal 1, TB.woah
48 |   end
49 | 
50 |   def test_2
51 |   end
52 | end
53 | 
54 | class TC < TLDR
55 |   def teardown
56 |   end
57 | 
58 |   def test_1
59 |   end
60 | 
61 |   def test_2
62 |     TB.woah += 1
63 |     sleep 0.1
64 |     assert_equal 1, TB.woah
65 |     TB.woah = 0
66 |   end
67 | end
68 | 
--------------------------------------------------------------------------------
/tests/fixture/run_these_together_superclasses.rb:
--------------------------------------------------------------------------------
 1 | class Super < TLDR
 2 |   run_these_together!
 3 | 
 4 |   @@nonsense = 0
 5 | 
 6 |   def teardown
 7 |     @@nonsense = 0
 8 |   end
 9 | end
10 | 
11 | class TA1 < Super
12 |   def test_1
13 |     @@nonsense += 1
14 |     sleep 0.1
15 |     assert_equal 1, @@nonsense
16 |   end
17 | end
18 | 
19 | class TB2 < Super
20 |   def test_1
21 |     @@nonsense += 1
22 |     sleep 0.1
23 |     assert_equal 1, @@nonsense
24 |   end
25 | end
26 | 
--------------------------------------------------------------------------------
/tests/fixture/seed.rb:
--------------------------------------------------------------------------------
 1 | class SeedTest < TLDR
 2 |   def test_stuff
 3 |     assert true
 4 |   end
 5 | 
 6 |   def test_skip
 7 |     skip
 8 |   end
 9 | end
10 | 
--------------------------------------------------------------------------------
/tests/fixture/several_fails.rb:
--------------------------------------------------------------------------------
 1 | class Fails < TLDR
 2 |   def test_f1
 3 |     assert false
 4 |   end
 5 | 
 6 |   def test_f2
 7 |     assert_equal 1, 2
 8 |   end
 9 | 
10 |   def test_f3
11 |     assert_equal false, true
12 |   end
13 | 
14 |   def test_f4
15 |     assert_equal false, true, "false isn't true?!"
16 |   end
17 | 
18 |   def test_s1
19 |     skip
20 |   end
21 | end
22 | 
--------------------------------------------------------------------------------
/tests/fixture/skip.rb:
--------------------------------------------------------------------------------
1 | class SuccessTest < TLDR
2 |   def test_skips
3 |     skip
4 | 
5 |     assert false
6 |   end
7 | end
8 | 
--------------------------------------------------------------------------------
/tests/fixture/slow.rb:
--------------------------------------------------------------------------------
 1 | # Designed to be run with: tldr --seed 1 "tests/fixture/slow.rb"
 2 | class Slow < TLDR
 3 |   def test_one
 4 |     zzz
 5 |   end
 6 | 
 7 |   def test_two
 8 |     zzz
 9 |   end
10 | 
11 |   def test_three
12 |     zzz
13 |     skip
14 |   end
15 | 
16 |   def test_four
17 |     zzz
18 |   end
19 | 
20 |   def test_five
21 |     zzz
22 |     assert_equal 1, 2
23 |   end
24 | 
25 |   def test_six
26 |     zzz
27 |     raise "💥"
28 |   end
29 | 
30 |   def test_seven
31 |     zzz
32 |   end
33 | 
34 |   def test_eight
35 |     zzz
36 |   end
37 | 
38 |   private
39 | 
40 |   def zzz
41 |     sleep 0.4
42 |   end
43 | end
44 | 
--------------------------------------------------------------------------------
/tests/fixture/subsubclass.rb:
--------------------------------------------------------------------------------
 1 | class SuperTest < TLDR
 2 | end
 3 | 
 4 | class SubTest < SuperTest
 5 |   def test_passes
 6 |     assert true
 7 |   end
 8 | end
 9 | 
10 | class SubSubTest < SubTest
11 |   def test_passes
12 |     assert true
13 |   end
14 | end
15 | 
--------------------------------------------------------------------------------
/tests/fixture/success.rb:
--------------------------------------------------------------------------------
1 | class SuccessTest < TLDR
2 |   def test_passes
3 |     assert true
4 |   end
5 | end
6 | 
--------------------------------------------------------------------------------
/tests/fixture/suite_summary.rb:
--------------------------------------------------------------------------------
 1 | class T1 < TLDR
 2 |   def test_1_1
 3 |   end
 4 | 
 5 |   def test_1_2
 6 |     skip
 7 |   end
 8 | 
 9 |   def test_1_3
10 |     assert false
11 |   end
12 | 
13 |   def test_1_4
14 |     raise "💥"
15 |   end
16 | end
17 | 
18 | class T2 < TLDR
19 |   def test_2_1
20 |   end
21 | 
22 |   def test_2_2
23 |     skip
24 |   end
25 | 
26 |   def test_2_3
27 |     assert false
28 |   end
29 | 
30 |   def test_2_4
31 |     raise "💥"
32 |   end
33 | end
34 | 
--------------------------------------------------------------------------------
/tests/fixture/suite_summary_too_slow.rb:
--------------------------------------------------------------------------------
 1 | class T1 < TLDR
 2 |   def test_1_1
 3 |     assert true
 4 |   end
 5 | end
 6 | 
 7 | class T2 < TLDR
 8 |   def test_2_1
 9 |     sleep 2
10 |   end
11 | end
12 | 
--------------------------------------------------------------------------------
/tests/fixture/warnings.rb:
--------------------------------------------------------------------------------
1 | class Warnful < TLDR
2 |   def test_warning
3 |   end
4 | 
5 |   def test_warning # standard:disable Lint/DuplicateMethods
6 |   end
7 | end
8 | 
--------------------------------------------------------------------------------
/tests/helpers_test.rb:
--------------------------------------------------------------------------------
 1 | require_relative "test_helper"
 2 | 
 3 | class HelpersTest < Minitest::Test
 4 |   def test_helpers
 5 |     result = TLDRunner.should_succeed "success.rb", "--helper tests/fixture/helper_b.rb --helper tests/fixture/helper_a.rb"
 6 | 
 7 |     assert_includes result.stdout, <<~MSG
 8 |       Helper B
 9 |       Helper A
10 |     MSG
11 |   end
12 | 
13 |   def test_helpers_glob
14 |     result = TLDRunner.should_succeed "success.rb", "--helper \"tests/fixture/helper_*.rb\""
15 | 
16 |     assert_includes result.stdout, "Helper A"
17 |     assert_includes result.stdout, "Helper B"
18 |   end
19 | end
20 | 
--------------------------------------------------------------------------------
/tests/hooks_test.rb:
--------------------------------------------------------------------------------
 1 | require_relative "test_helper"
 2 | 
 3 | class HooksTest < Minitest::Test
 4 |   def test_hooks
 5 |     result = TLDRunner.should_succeed "hooks.rb", "--no-parallel -n test_1,test_2"
 6 | 
 7 |     assert_includes result.stdout, <<~MSG
 8 |       A(B)C.
 9 |       A(B)C.
10 |     MSG
11 |   end
12 | 
13 |   def test_uncalled_around_hook
14 |     result = TLDRunner.should_fail "hooks.rb", "--no-parallel -n test_3"
15 | 
16 |     assert_includes result.stderr, "BadHook#around failed to yield or call the passed test block"
17 |   end
18 | end
19 | 
--------------------------------------------------------------------------------
/tests/line_number_test.rb:
--------------------------------------------------------------------------------
 1 | require_relative "test_helper"
 2 | 
 3 | class LineNumberTest < Minitest::Test
 4 |   def test_line_number_exact_hit
 5 |     result = TLDRunner.should_succeed "line_number.rb:2", "--emoji"
 6 | 
 7 |     assert_includes result.stdout, "😁"
 8 |     refute_includes result.stdout, "🫥"
 9 |     refute_includes result.stderr, "😡"
10 |   end
11 | 
12 |   def test_line_number_intra_method
13 |     result = TLDRunner.should_succeed "line_number.rb:3", "--emoji"
14 | 
15 |     assert_includes result.stdout, "😁"
16 |     refute_includes result.stdout, "🫥"
17 |     refute_includes result.stderr, "😡"
18 |   end
19 | 
20 |   def test_line_number_end_of_method
21 |     result = TLDRunner.should_succeed "line_number.rb:4", "--emoji"
22 | 
23 |     assert_includes result.stdout, "😁"
24 |     refute_includes result.stdout, "🫥"
25 |     refute_includes result.stderr, "😡"
26 |   end
27 | 
28 |   def test_line_number_two_methods
29 |     result = TLDRunner.should_fail "line_number.rb:3:11", "--emoji"
30 | 
31 |     assert_includes result.stdout, "😁"
32 |     assert_includes result.stdout, "😡"
33 |     refute_includes result.stdout, "🫥"
34 |   end
35 | 
36 |   def test_line_number_three_methods
37 |     result = TLDRunner.should_fail "line_number.rb:3:8:11", "--emoji"
38 | 
39 |     assert_includes result.stdout, "😁"
40 |     assert_includes result.stdout, "🫥"
41 |     assert_includes result.stdout, "😡"
42 |   end
43 | 
44 |   def test_line_number_three_methods_over_two_patterns
45 |     result = TLDRunner.should_fail ["line_number.rb:3:11", "line_number.rb:8"], "--emoji"
46 | 
47 |     assert_includes result.stdout, "😁"
48 |     assert_includes result.stdout, "🫥"
49 |     assert_includes result.stdout, "😡"
50 |   end
51 | 
52 |   def test_line_number_nonsense
53 |     result = TLDRunner.should_succeed "line_number.rb:999:42:5", "--emoji"
54 | 
55 |     refute_includes result.stdout, "😁"
56 |     refute_includes result.stdout, "🫥"
57 |     refute_includes result.stderr, "😡"
58 |   end
59 | end
60 | 
--------------------------------------------------------------------------------
/tests/name_filters_test.rb:
--------------------------------------------------------------------------------
 1 | require_relative "test_helper"
 2 | 
 3 | class NameFiltersTest < Minitest::Test
 4 |   def test_pattern
 5 |     result = TLDRunner.should_succeed "name_filters.rb", "--name /my_.*_case/"
 6 | 
 7 |     assert_includes result.stdout, "1️⃣"
 8 |     assert_includes result.stdout, "2️⃣"
 9 |     refute_includes result.stdout, "3️⃣"
10 |   end
11 | 
12 |   def test_single
13 |     result = TLDRunner.should_succeed "name_filters.rb", "--name test_my_second_case"
14 | 
15 |     refute_includes result.stdout, "1️⃣"
16 |     assert_includes result.stdout, "2️⃣"
17 |     refute_includes result.stdout, "3️⃣"
18 |   end
19 | 
20 |   def test_with_class_name
21 |     result = TLDRunner.should_succeed "name_filters.rb", "--name NameFilters#test_a_third_case"
22 | 
23 |     refute_includes result.stdout, "1️⃣"
24 |     refute_includes result.stdout, "2️⃣"
25 |     assert_includes result.stdout, "3️⃣"
26 |   end
27 | 
28 |   def test_with_pattern_with_comma
29 |     result = TLDRunner.should_succeed "name_filters.rb", "--name \"/test_my_[second]{1,6}_case/,test_my_first_case,/test_a_[third]{4,5}_case/\""
30 | 
31 |     assert_includes result.stdout, "1️⃣"
32 |     assert_includes result.stdout, "2️⃣"
33 |     assert_includes result.stdout, "3️⃣"
34 |   end
35 | 
36 |   def test_with_class_name_and_regex
37 |     result = TLDRunner.should_succeed "name_filters.rb", "--name \"/.*Filters#test_(a|my)_(second|third)_case/\""
38 | 
39 |     refute_includes result.stdout, "1️⃣"
40 |     assert_includes result.stdout, "2️⃣"
41 |     assert_includes result.stdout, "3️⃣"
42 |   end
43 | 
44 |   def test_with_name_and_line_number_should_require_both_to_match
45 |     result = TLDRunner.should_succeed "name_filters.rb:7:11", "--name \"/my_.*_case/\""
46 | 
47 |     refute_includes result.stdout, "1️⃣"
48 |     assert_includes result.stdout, "2️⃣"
49 |     refute_includes result.stdout, "3️⃣"
50 |   end
51 | 
52 |   def test_multiple
53 |     result = TLDRunner.should_succeed "name_filters.rb", "--name /second/,NameFilters#test_a_third_case -n test_my_first_case"
54 | 
55 |     assert_includes result.stdout, "1️⃣"
56 |     assert_includes result.stdout, "2️⃣"
57 |     assert_includes result.stdout, "3️⃣"
58 |   end
59 | end
60 | 
--------------------------------------------------------------------------------
/tests/prepend_test.rb:
--------------------------------------------------------------------------------
 1 | require_relative "test_helper"
 2 | require "fileutils"
 3 | 
 4 | class PrependTest < Minitest::Test
 5 |   def test_setting_explicitly_by_file_a
 6 |     result = TLDRunner.should_succeed ["folder/a.rb", "folder/b.rb"], "--prepend tests/fixture/folder/a.rb --seed 1"
 7 | 
 8 |     assert_these_appear_before_these result.stdout, ["A1", "A2", "A3"], ["B1", "B2", "B3"]
 9 |   end
10 | 
11 |   def test_setting_explicitly_by_line_number
12 |     result = TLDRunner.should_succeed ["folder/a.rb", "folder/b.rb"], "--prepend tests/fixture/folder/a.rb:7 --seed 1"
13 | 
14 |     assert_these_appear_before_these result.stdout, ["A2"], ["A1", "A3", "B1", "B2", "B3"]
15 |   end
16 | 
17 |   def test_setting_explicitly_by_multiple
18 |     result = TLDRunner.should_succeed ["folder/a.rb", "folder/b.rb"], "--prepend tests/fixture/folder/a.rb:3:12 --prepend tests/fixture/folder/b.rb:8 --seed 1"
19 | 
20 |     assert_these_appear_before_these result.stdout, ["A1", "A3", "B2"], ["A2", "B1", "B3"]
21 |   end
22 | 
23 |   def test_setting_explicitly_by_file_b
24 |     result = TLDRunner.should_succeed ["folder/a.rb", "folder/b.rb"], "--prepend tests/fixture/folder/b.rb --seed 1"
25 | 
26 |     assert_these_appear_before_these result.stdout, ["B1", "B2", "B3"], ["A1", "A2", "A3"]
27 |   end
28 | 
29 |   def test_modifying_file_changes_prepend_default
30 |     FileUtils.touch("tests/fixture/folder/a.rb")
31 |     result = TLDRunner.should_succeed ["folder/a.rb", "folder/b.rb"], "--seed 1"
32 |     assert_these_appear_before_these result.stdout, ["A1", "A2", "A3"], ["B1", "B2", "B3"]
33 | 
34 |     FileUtils.touch("tests/fixture/folder/b.rb")
35 |     result = TLDRunner.should_succeed ["folder/a.rb", "folder/b.rb"], "--seed 1"
36 |     assert_these_appear_before_these result.stdout, ["B1", "B2", "B3"], ["A1", "A2", "A3"]
37 |   end
38 | 
39 |   def test_no_prepend_does_not_prepend
40 |     result = TLDRunner.should_succeed ["folder/a.rb", "folder/b.rb"], "--seed 1 --no-prepend"
41 | 
42 |     assert_strings_appear_in_this_order result.stdout, ["B3", "B2", "A2", "B1", "A1", "A3"]
43 |   end
44 | 
45 |   def test_globs
46 |     result = TLDRunner.should_succeed ["folder/*.rb", "c.rb"], "--prepend \"tests/fixture/folder/*.rb\" --seed 1"
47 | 
48 |     assert_these_appear_before_these result.stdout, ["A1", "A2", "A3", "B1", "B2", "B3"], ["C1", "C2", "C3"]
49 |   end
50 | end
51 | 
--------------------------------------------------------------------------------
/tests/rake_task_test.rb:
--------------------------------------------------------------------------------
 1 | require_relative "test_helper"
 2 | 
 3 | class RakeTaskTest < Minitest::Test
 4 |   def test_running_rake
 5 |     result = TLDRunner.run_command("cd example/b && BUNDLE_GEMFILE=\"Gemfile\" TLDR_OPTS=\"--seed 1\" bundle exec rake")
 6 | 
 7 |     assert_empty result.stderr
 8 |     assert result.success?
 9 | 
10 |     assert_includes result.stdout, <<~MSG
11 |       neat!
12 |       Command: bundle exec tldr --seed 1
13 |       --seed 1
14 | 
15 |       Running:
16 | 
17 |       .
18 |     MSG
19 |   end
20 | 
21 |   def test_running_custom_rake_task
22 |     result = TLDRunner.run_command("cd example/b && TLDR_OPTS=\"--seed 1\" bundle exec rake safe_tests")
23 | 
24 |     assert_includes result.stdout, <<~MSG
25 |       cool!
26 |       Command: bundle exec tldr --seed 1 --helper "safe/helper.rb" --load-path "lib" --load-path "safe" "safe/big_test.rb"
27 |       --seed 1
28 | 
29 |       Running:
30 | 
31 |       .
32 |     MSG
33 |   end
34 | 
35 |   def test_running_custom_base_path
36 |     result = TLDRunner.run_command("cd example/c && BUNDLE_GEMFILE=\"../b/Gemfile\" TLDR_OPTS=\"--seed 1\" bundle exec rake b_tests")
37 | 
38 |     assert_includes result.stdout, <<~MSG
39 |       neat!
40 |       Command: bundle exec tldr --seed 1 --base-path "../b"
41 |       --seed 1
42 | 
43 |       Running:
44 | 
45 |       .
46 |     MSG
47 |   end
48 | 
49 |   def test_running_default_base_path_when_custom_also_exists
50 |     result = TLDRunner.run_command("cd example/c && BUNDLE_GEMFILE=\"Gemfile\" TLDR_OPTS=\"--seed 1\" bundle exec rake tldr")
51 | 
52 |     assert_empty result.stderr
53 |     assert result.success?
54 |     assert_includes result.stdout, <<~MSG
55 |       👓
56 |       Command: bundle exec tldr --seed 1 --helper "spec/spec_helper.rb" "spec/math_spec.rb"
57 |       --seed 1
58 | 
59 |       Running:
60 | 
61 |       .
62 |     MSG
63 |   end
64 | end
65 | 
--------------------------------------------------------------------------------
/tests/run_these_together_test.rb:
--------------------------------------------------------------------------------
 1 | require_relative "test_helper"
 2 | 
 3 | class RunTheseTogetherTest < Minitest::Test
 4 |   def test_running_these_together
 5 |     result = TLDRunner.should_succeed "run_these_together.rb"
 6 | 
 7 |     assert_includes result.stdout, "6 test methods"
 8 |   end
 9 | 
10 |   def test_running_these_together_specified_by_superclass
11 |     result = TLDRunner.should_succeed "run_these_together_superclasses.rb"
12 | 
13 |     assert_includes result.stdout, "2 test methods"
14 |   end
15 | end
16 | 
--------------------------------------------------------------------------------
/tests/seed_test.rb:
--------------------------------------------------------------------------------
 1 | require_relative "test_helper"
 2 | 
 3 | class SeedTest < Minitest::Test
 4 |   def test_order_1
 5 |     result = TLDRunner.should_succeed "seed.rb", "--seed 1"
 6 | 
 7 |     assert_includes result.stdout, <<~MSG
 8 |       Skipped tests:
 9 | 
10 |         - SeedTest#test_skip [tests/fixture/seed.rb:6]
11 |     MSG
12 |     assert_equal 0, result.exit_code
13 |     assert_includes result.stdout, "--seed 1"
14 |     assert_includes result.stdout, "S."
15 |   end
16 | 
17 |   def test_order_2
18 |     result = TLDRunner.should_succeed "seed.rb", "--seed 2"
19 | 
20 |     assert_includes result.stdout, <<~MSG
21 |       Skipped tests:
22 | 
23 |         - SeedTest#test_skip [tests/fixture/seed.rb:6]
24 |     MSG
25 |     assert_equal 0, result.exit_code
26 |     assert_includes result.stdout, "--seed 2"
27 |     assert_includes result.stdout, ".S"
28 |   end
29 | end
30 | 
--------------------------------------------------------------------------------
/tests/subsubclass_test.rb:
--------------------------------------------------------------------------------
 1 | require_relative "test_helper"
 2 | 
 3 | class SubSubclassTest < Minitest::Test
 4 |   def test_descendants
 5 |     result = TLDRunner.should_succeed "subsubclass.rb"
 6 | 
 7 |     assert_includes result.stdout, <<~MSG
 8 |       ..
 9 |     MSG
10 |   end
11 | end
12 | 
--------------------------------------------------------------------------------
/tests/suite_summary_test.rb:
--------------------------------------------------------------------------------
 1 | require_relative "test_helper"
 2 | 
 3 | class SuiteSummaryTest < Minitest::Test
 4 |   def test_summary
 5 |     result = TLDRunner.should_fail "suite_summary.rb"
 6 | 
 7 |     assert_match(/Finished in \d+ms./, result.stdout)
 8 |     assert_includes result.stdout, <<~MSG.chomp
 9 |       2 test classes, 8 test methods, 2 failures, 2 errors, 2 skips
10 |     MSG
11 |   end
12 | 
13 |   def test_verbose_summary_too_slow
14 |     result = TLDRunner.should_fail "suite_summary_too_slow.rb", "--timeout --print-interrupted-test-backtraces"
15 | 
16 |     assert_match(/Finished in \d+ms./, result.stdout)
17 |     assert_includes result.stdout, <<~MSG.chomp
18 |       1 test class, 1 test method, 0 failures, 0 errors, 0 skips
19 |     MSG
20 | 
21 |     assert_includes scrub_time(normalise_abs_paths(result.stderr)), <<~MSG.chomp
22 |       1 test was cancelled in progress:
23 |         XXXms - T2#test_2_1 [tests/fixture/suite_summary_too_slow.rb:8]
24 |           Backtrace at the point of cancellation:
25 |           /path/to/tldr/tests/fixture/suite_summary_too_slow.rb:9:in #{(TLDR::RubyUtil.version >= "3.4") ? "'Kernel#sleep'" : "`sleep'"}
26 |           /path/to/tldr/tests/fixture/suite_summary_too_slow.rb:9:in #{(TLDR::RubyUtil.version >= "3.4") ? "'T2#test_2_1'" : "`test_2_1'"}
27 |     MSG
28 |   end
29 | 
30 |   private
31 | 
32 |   def scrub_time string
33 |     string.gsub(/(\d+)ms/, "XXXms")
34 |   end
35 | 
36 |   def normalise_abs_paths string
37 |     parent_of_lib_folder = File.expand_path(File.join(__dir__, "../.."))
38 |     string.gsub(parent_of_lib_folder, "/path/to")
39 |   end
40 | end
41 | 
--------------------------------------------------------------------------------
/tests/test_helper.rb:
--------------------------------------------------------------------------------
  1 | $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
  2 | require "tldr"
  3 | require "open3"
  4 | require "tmpdir"
  5 | 
  6 | require "minitest/autorun"
  7 | 
  8 | class Minitest::Test
  9 |   parallelize_me!
 10 |   make_my_diffs_pretty!
 11 | 
 12 |   protected
 13 | 
 14 |   private
 15 | 
 16 |   def with_temp_file(name, contents, &blk)
 17 |     Dir.mktmpdir do |dir|
 18 |       path = File.join(dir, name)
 19 |       File.write(path, contents)
 20 |       yield File.absolute_path(path)
 21 |     end
 22 |   end
 23 | 
 24 |   def assert_includes_all haystack, needles
 25 |     unless needles.all? { |needle| haystack.include?(needle) }
 26 |       raise Minitest::Assertion, "Expected all of #{needles.inspect} to be found in in:\n\n---\n#{haystack}\n---"
 27 |     end
 28 |   end
 29 | 
 30 |   def assert_includes_none haystack, needles
 31 |     unless needles.none? { |needle| haystack.include?(needle) }
 32 |       raise Minitest::Assertion, "Expected none of #{needles.inspect} to be found in in:\n\n---\n#{haystack}\n---"
 33 |     end
 34 |   end
 35 | 
 36 |   def assert_strings_appear_in_this_order haystack, needles
 37 |     og_haystack = haystack
 38 |     needles.each.with_index do |needle, i|
 39 |       index = haystack.index(needle)
 40 |       raise Minitest::Assertion, "#{needle.inspect} (string ##{i + 1} in #{needles.inspect}) not found in order in:\n\n---\n#{og_haystack}\n---" unless index
 41 | 
 42 |       haystack = haystack[(index + needle.length)..]
 43 |     end
 44 |   end
 45 | 
 46 |   def assert_these_appear_before_these haystack, before, after
 47 |     before.each.with_index do |needle, i|
 48 |       index = haystack.index(needle)
 49 | 
 50 |       if (after_needle = after.find { |after_needle| haystack.index(after_needle) < index })
 51 |         raise Minitest::Assertion, "#{needle.inspect} was expected to be found before #{after_needle.inspect} in:\n\n---\n#{haystack}\n---"
 52 |       end
 53 |     end
 54 |   end
 55 | end
 56 | 
 57 | module TLDRunner
 58 |   Result = Struct.new(:stdout, :stderr, :exit_code, :success?, keyword_init: true)
 59 | 
 60 |   def self.should_succeed files, options = nil
 61 |     run(files, options).tap do |result|
 62 |       if !result.success?
 63 |         raise <<~MSG
 64 |           Ran #{files.inspect} and expected success, but exited code #{result.exit_code}
 65 | 
 66 |           stdout:
 67 |           #{result.stdout}
 68 | 
 69 |           stderr:
 70 |           #{result.stderr}
 71 |         MSG
 72 |       end
 73 |     end
 74 |   end
 75 | 
 76 |   def self.should_fail files, options = nil
 77 |     run(files, options).tap do |result|
 78 |       if result.success?
 79 |         raise <<~MSG
 80 |           Ran #{files.inspect} and expected failure, but exited code #{result.exit_code}
 81 | 
 82 |           stdout:
 83 |           #{result.stdout}
 84 | 
 85 |           stderr:
 86 |           #{result.stderr}
 87 |         MSG
 88 |       end
 89 |     end
 90 |   end
 91 | 
 92 |   def self.run files, options
 93 |     files = Array(files).map { |file| File.expand_path("fixture/#{file}", __dir__) }
 94 | 
 95 |     stdout, stderr, status = Open3.capture3 <<~CMD
 96 |       bundle exec tldr #{files.join(" ")} #{options}
 97 |     CMD
 98 | 
 99 |     Result.new(
100 |       stdout: stdout.chomp,
101 |       stderr: stderr.chomp,
102 |       exit_code: status.exitstatus,
103 |       success?: status.success?
104 |     )
105 |   end
106 | 
107 |   def self.run_command command
108 |     stdout, stderr, status = Open3.capture3(command)
109 | 
110 |     Result.new(
111 |       stdout: stdout.chomp,
112 |       stderr: stderr.chomp,
113 |       exit_code: status.exitstatus,
114 |       success?: status.success?
115 |     )
116 |   end
117 | end
118 | 
119 | class AssertionTestCase < Minitest::Test
120 |   def setup
121 |     SuperDiff.configure do |config|
122 |       config.color_enabled = false
123 |     end
124 |   end
125 | 
126 |   protected
127 | 
128 |   def should_fail message = nil
129 |     e = assert_raises(TLDR::Failure) {
130 |       yield
131 |     }
132 | 
133 |     if message.is_a?(String)
134 |       assert_includes e.message, message
135 |     elsif message.is_a?(Regexp)
136 |       assert_match message, e.message
137 |     elsif !message.nil?
138 |       fail "Unknown message type: #{message.inspect}"
139 |     end
140 | 
141 |     e
142 |   end
143 | end
144 | 
--------------------------------------------------------------------------------
/tests/tldr/argv_parser_test.rb:
--------------------------------------------------------------------------------
 1 | require_relative "../test_helper"
 2 | 
 3 | class ArgvParserTest < Minitest::Test
 4 |   def test_parsing_argv
 5 |     result = TLDR::ArgvParser.new.parse([
 6 |       "bar.rb",
 7 |       "--seed", "42",
 8 |       "--timeout", "1.4",
 9 |       "-v",
10 |       "--print-interrupted-test-backtraces",
11 |       "foo.rb:3",
12 |       "--reporter", "TLDR::Reporters::Base",
13 |       "--no-helper",
14 |       "--helper", "spec/spec_helper.rb",
15 |       "-l", "lib",
16 |       "-l", "vendor,spec",
17 |       "--no-parallel",
18 |       "--name", "foo",
19 |       "--load-path", "app",
20 |       "-n", "bar,baz",
21 |       "--yes-i-know"
22 |     ])
23 | 
24 |     assert_equal ["bar.rb", "foo.rb:3"], result.paths
25 |     assert_equal 42, result.seed
26 |     assert_equal result.timeout, 1.4
27 |     assert result.no_helper
28 |     assert result.verbose
29 |     assert_equal "TLDR::Reporters::Base", result.reporter
30 |     assert_equal ["spec/spec_helper.rb"], result.helper_paths
31 |     assert_equal ["lib", "vendor", "spec", "app"], result.load_paths
32 |     refute result.parallel
33 |     assert_equal ["foo", "bar", "baz"], result.names
34 |     assert result.yes_i_know
35 |   end
36 | 
37 |   def test_defaults
38 |     result = TLDR::ArgvParser.new.parse([])
39 | 
40 |     assert_equal Dir["test/**/*_test.rb", "test/**/test_*.rb"], result.paths
41 |     assert_includes 0..10_000, result.seed
42 |     assert_equal result.timeout, -1
43 |     refute result.no_helper
44 |     refute result.verbose
45 |     refute result.print_interrupted_test_backtraces
46 |     assert_equal "TLDR::Reporters::Default", result.reporter
47 |     assert_equal ["test/helper.rb"], result.helper_paths
48 |     assert_equal ["lib", "test"], result.load_paths
49 |     assert result.parallel
50 |     assert_equal [], result.names
51 |     refute result.yes_i_know
52 |   end
53 | 
54 |   def test_timeout_arg_specifically
55 |     assert_equal(-1, TLDR::ArgvParser.new.parse([]).timeout)
56 |     assert_equal 1.6, TLDR::ArgvParser.new.parse(["--timeout", "1.6"]).timeout
57 |     assert_equal(-1, TLDR::ArgvParser.new.parse(["--no-timeout"]).timeout)
58 |     assert_equal TLDR::Config::DEFAULT_TIMEOUT, TLDR::ArgvParser.new.parse(["--timeout"]).timeout
59 |     # last-in wins:
60 |     assert_equal 1.4, TLDR::ArgvParser.new.parse(["--no-timeout", "--timeout", "1.4"]).timeout
61 |     assert_equal(-1, TLDR::ArgvParser.new.parse(["--timeout", "1.4", "--no-timeout"]).timeout)
62 |     assert_equal(TLDR::Config::DEFAULT_TIMEOUT, TLDR::ArgvParser.new.parse(["--no-timeout", "--timeout", "--seed", "42"]).timeout)
63 |   end
64 | end
65 | 
--------------------------------------------------------------------------------
/tests/tldr/assertions_test.rb:
--------------------------------------------------------------------------------
  1 | require_relative "../test_helper"
  2 | 
  3 | class AssertionsTest < AssertionTestCase
  4 |   class Asserty
  5 |     include TLDR::Assertions
  6 |   end
  7 | 
  8 |   def setup
  9 |     super
 10 |     @subject = Asserty.new
 11 |   end
 12 | 
 13 |   def test_assert
 14 |     @subject.assert true
 15 |     should_fail "Expected false to be truthy" do
 16 |       @subject.assert false
 17 |     end
 18 |     should_fail "Custom" do
 19 |       @subject.assert false, "Custom"
 20 |     end
 21 |   end
 22 | 
 23 |   def test_refute
 24 |     @subject.refute false
 25 |     should_fail "Expected true to not be truthy" do
 26 |       @subject.refute true
 27 |     end
 28 |     should_fail "Custom" do
 29 |       @subject.refute true, "Custom"
 30 |     end
 31 |   end
 32 | 
 33 |   def test_assert_empty
 34 |     @subject.assert_empty []
 35 |     should_fail "Expected [1] to be empty" do
 36 |       @subject.assert_empty [1]
 37 |     end
 38 |     should_fail "Neat\nExpected [2] to be empty" do
 39 |       @subject.assert_empty [2], "Neat"
 40 |     end
 41 |   end
 42 | 
 43 |   def test_refute_empty
 44 |     @subject.refute_empty [1]
 45 |     should_fail "Expected [] to not be empty" do
 46 |       @subject.refute_empty []
 47 |     end
 48 |     should_fail "Neat\nExpected [] to not be empty" do
 49 |       @subject.refute_empty [], "Neat"
 50 |     end
 51 |   end
 52 | 
 53 |   def test_assert_equal
 54 |     @subject.assert_equal 42, 42
 55 |     msg = <<~MSG.chomp
 56 |       Differing numbers.
 57 | 
 58 |       Expected: 41
 59 |         Actual: 42
 60 |     MSG
 61 |     should_fail msg do
 62 |       @subject.assert_equal 41, 42
 63 |     end
 64 |   end
 65 | 
 66 |   def test_refute_equal
 67 |     @subject.refute_equal 41, 42
 68 | 
 69 |     should_fail "Expected 42 to not be equal to 42" do
 70 |       @subject.refute_equal 42, 42
 71 |     end
 72 |   end
 73 | 
 74 |   def test_assert_in_delta
 75 |     @subject.assert_in_delta 0.5, 0.51, 0.02
 76 |     should_fail "Expected |0.5 - 0.4| (0.09999999999999998) to be within 0.01" do
 77 |       @subject.assert_in_delta 0.5, 0.4, 0.01
 78 |     end
 79 |   end
 80 | 
 81 |   def test_refute_in_delta
 82 |     @subject.refute_in_delta 0.5, 0.51, 0.01
 83 |     should_fail "Expected |0.5 - 0.4| (0.09999999999999998) to not be within 0.2" do
 84 |       @subject.refute_in_delta 0.5, 0.4, 0.2
 85 |     end
 86 |   end
 87 | 
 88 |   def test_assert_in_epsilon
 89 |     @subject.assert_in_epsilon 0.5, 0.51, 0.04
 90 |     should_fail "Expected |0.5 - 0.4| (0.09999999999999998) to be within 0.04000000000000001" do
 91 |       @subject.assert_in_epsilon 0.5, 0.4, 0.1
 92 |     end
 93 |   end
 94 | 
 95 |   def test_refute_in_epsilon
 96 |     @subject.refute_in_epsilon 0.5, 0.4, 0.1
 97 |     should_fail "Expected |0.5 - 0.51| (0.010000000000000009) to not be within 0.02" do
 98 |       @subject.refute_in_epsilon 0.5, 0.51, 0.04
 99 |     end
100 |   end
101 | 
102 |   def test_assert_includes
103 |     @subject.assert_includes "food", "foo"
104 |     should_fail "Expected \"drink\" to include \"foo\"" do
105 |       @subject.assert_includes "drink", "foo"
106 |     end
107 |     should_fail(/Expected # \(Object\) to respond to :include?/) do
108 |       @subject.assert_includes Object.new, "stuff"
109 |     end
110 |   end
111 | 
112 |   def test_refute_includes
113 |     @subject.refute_includes "drink", "foo"
114 |     should_fail "Expected \"food\" to not include \"foo\"" do
115 |       @subject.refute_includes "food", "foo"
116 |     end
117 |     should_fail(/Expected # \(Object\) to respond to :include?/) do
118 |       @subject.refute_includes Object.new, "stuff"
119 |     end
120 |   end
121 | 
122 |   def test_assert_instance_of
123 |     @subject.assert_instance_of Object, Object.new
124 |     should_fail(/Expected # to be an instance of String, not Object/) do
125 |       @subject.assert_instance_of String, Object.new
126 |     end
127 |   end
128 | 
129 |   def test_refute_instance_of
130 |     @subject.refute_instance_of String, Object.new
131 |     should_fail(/Expected # to not be an instance of Object/) do
132 |       @subject.refute_instance_of Object, Object.new
133 |     end
134 |   end
135 | 
136 |   def test_assert_kind_of
137 |     @subject.assert_kind_of Object, String.new("hi") # standard:disable Performance/UnfreezeString
138 |     should_fail(/Expected # to be a kind of String, not Object/) do
139 |       @subject.assert_kind_of String, Object.new
140 |     end
141 |   end
142 | 
143 |   def test_refute_kind_of
144 |     @subject.refute_kind_of String, Object.new
145 |     should_fail(/Expected "hi" to not be a kind of Object/) do
146 |       @subject.refute_kind_of Object, String.new("hi") # standard:disable Performance/UnfreezeString
147 |     end
148 |   end
149 | 
150 |   class Matchless
151 |     if instance_methods.include?(:=~)
152 |       undef_method :=~
153 |     end
154 |   end
155 | 
156 |   def test_assert_match
157 |     @subject.assert_match(/foo/, "food")
158 |     result = @subject.assert_match("foo", "food")
159 |     assert_equal "foo", result[0]
160 |     should_fail "Expected \"drink\" to match /foo/" do
161 |       @subject.assert_match(/foo/, "drink")
162 |     end
163 |     should_fail(/Expected # \(AssertionsTest::Matchless\) to respond to :=~/) do
164 |       @subject.assert_match Matchless.new, "stuff"
165 |     end
166 |   end
167 | 
168 |   def test_refute_match
169 |     @subject.refute_match(/foo/, "drink")
170 |     should_fail "Expected \"food\" to not match /foo/" do
171 |       @subject.refute_match(/foo/, "food")
172 |     end
173 |     should_fail(/Expected # \(AssertionsTest::Matchless\) to respond to :=~/) do
174 |       @subject.refute_match Matchless.new, "stuff"
175 |     end
176 |   end
177 | 
178 |   def test_assert_nil
179 |     @subject.assert_nil nil
180 |     should_fail "Expected false to be nil" do
181 |       @subject.assert_nil false
182 |     end
183 |     should_fail "Custom" do
184 |       @subject.assert_nil 42, "Custom"
185 |     end
186 |   end
187 | 
188 |   def test_refute_nil
189 |     @subject.refute_nil false
190 |     should_fail "Expected nil to not be nil" do
191 |       @subject.refute_nil nil
192 |     end
193 |     should_fail "Custom" do
194 |       @subject.refute_nil nil, "Custom"
195 |     end
196 |   end
197 | 
198 |   def test_assert_operator
199 |     @subject.assert_operator 1, :<, 2
200 |     should_fail "Expected 1 to be > 2" do
201 |       @subject.assert_operator 1, :>, 2
202 |     end
203 |   end
204 | 
205 |   def test_refute_operator
206 |     @subject.refute_operator 1, :>, 2
207 |     should_fail "Expected 1 to not be < 2" do
208 |       @subject.refute_operator 1, :<, 2
209 |     end
210 |   end
211 | 
212 |   def test_assert_output
213 |     @subject.assert_output "foo\n", "bar\n" do
214 |       puts "foo"
215 |       warn "bar"
216 |     end
217 |     @subject.assert_output(/fo/, /ar/) do
218 |       puts "foo"
219 |       warn "bar"
220 |     end
221 |     should_fail(/Expected: "qux\\n"/) do
222 |       @subject.assert_output "baz\n", "qux\n" do
223 |         puts "foo"
224 |         warn "bar"
225 |       end
226 |     end
227 |     should_fail(/Expected: "baz\\n"/) do
228 |       @subject.assert_output "baz\n", "bar\n" do
229 |         puts "foo"
230 |         warn "bar"
231 |       end
232 |     end
233 |   end
234 | 
235 |   def test_assert_path_exists
236 |     @subject.assert_path_exists "./tldr.gemspec"
237 |     should_fail "Expected \"./lolnope\" to exist" do
238 |       @subject.assert_path_exists "./lolnope"
239 |     end
240 |   end
241 | 
242 |   def test_refute_path_exists
243 |     @subject.refute_path_exists "./lolnope"
244 |     should_fail "Expected \"./tldr.gemspec\" to not exist" do
245 |       @subject.refute_path_exists "./tldr.gemspec"
246 |     end
247 |   end
248 | 
249 |   def test_assert_pattern
250 |     @subject.assert_pattern { [1, 2, 3] => [Integer, Integer, Integer] }
251 |     should_fail "Expected pattern to match, but NoMatchingPatternError was raised: [1, \"two\", 3]: Integer === \"two\" does not return true" do
252 |       @subject.assert_pattern { [1, "two", 3] => [Integer, Integer, Integer] }
253 |     end
254 |     should_fail "Custom\nExpected pattern to match, but NoMatchingPatternError was raised: [1, \"two\", 3]: Integer === \"two\" does not return true" do
255 |       @subject.assert_pattern("Custom") { [1, "two", 3] => [Integer, Integer, Integer] }
256 |     end
257 |   end
258 | 
259 |   def test_refute_pattern
260 |     @subject.refute_pattern { [1, "two", 3] => [Integer, Integer, Integer] }
261 |     should_fail "Expected pattern not to match, but NoMatchingPatternError was not raised" do
262 |       @subject.refute_pattern { [1, 2, 3] => [Integer, Integer, Integer] }
263 |     end
264 |   end
265 | 
266 |   def test_assert_predicate
267 |     @subject.assert_predicate 1, :odd?
268 |     should_fail "Expected 2 to be odd?" do
269 |       @subject.assert_predicate 2, :odd?
270 |     end
271 |   end
272 | 
273 |   def test_refute_predicate
274 |     @subject.refute_predicate 2, :odd?
275 |     should_fail "Expected 1 to not be odd?" do
276 |       @subject.refute_predicate 1, :odd?
277 |     end
278 |   end
279 | 
280 |   def test_assert_raises
281 |     e = @subject.assert_raises(ArgumentError) { raise ArgumentError, "hi" }
282 |     assert_equal e.message, "hi"
283 |     @subject.assert_raises(IOError, ArgumentError) { raise ArgumentError }
284 |     nested_e = assert_raises TLDR::Failure do
285 |       @subject.assert_raises {
286 |         @subject.assert_empty [1]
287 |       }
288 |     end
289 |     assert_equal "Expected [1] to be empty", nested_e.message
290 |     should_fail "StandardError expected but nothing was raised" do
291 |       @subject.assert_raises {}
292 |     end
293 |     msg = <<~MSG
294 |       [TypeError] exception expected, not
295 |       Class: 
296 |       Message: <"lol">
297 |       ---Backtrace---
298 |     MSG
299 |     should_fail msg do
300 |       @subject.assert_raises(TypeError) { raise IOError, "lol" }
301 |     end
302 |     assert_raises(TLDR::Skip) {
303 |       @subject.assert_raises {
304 |         raise TLDR::Skip
305 |       }
306 |     }
307 |     msg2 = <<~MSG
308 |       Should've been different
309 |       [IOError, ArgumentError] exception expected, not
310 |       Class: 
311 |       Message: <"TypeError">
312 |       ---Backtrace---
313 |     MSG
314 |     should_fail msg2 do
315 |       @subject.assert_raises(IOError, ArgumentError, "Should've been different") { raise TypeError }
316 |     end
317 |   end
318 | 
319 |   def test_assert_respond_to
320 |     @subject.assert_respond_to "foo", :length
321 |     should_fail "Expected \"foo\" (String) to respond to :pizza" do
322 |       @subject.assert_respond_to "foo", :pizza
323 |     end
324 |     should_fail "Custom.\nExpected \"foo\" (String) to respond to :pizza" do
325 |       @subject.assert_respond_to "foo", :pizza, "Custom."
326 |     end
327 |   end
328 | 
329 |   def test_refute_respond_to
330 |     @subject.refute_respond_to "foo", :pizza
331 |     should_fail "Expected \"foo\" (String) to not respond to :length" do
332 |       @subject.refute_respond_to "foo", :length
333 |     end
334 |     should_fail "Custom.\nExpected \"foo\" (String) to not respond to :length" do
335 |       @subject.refute_respond_to "foo", :length, "Custom."
336 |     end
337 |   end
338 | 
339 |   def test_assert_same
340 |     obj1 = Object.new
341 |     obj2 = Object.new
342 |     @subject.assert_same obj1, obj1
343 |     e = should_fail do
344 |       @subject.assert_same obj1, obj2
345 |     end
346 |     assert_includes e.message, "Expected objects to be the same, but weren't"
347 |     assert_match(/Expected: # \(oid=#{obj1.object_id}\)/, e.message)
348 |     assert_match(/Actual: # \(oid=#{obj2.object_id}\)/, e.message)
349 |   end
350 | 
351 |   def test_refute_same
352 |     obj1 = Object.new
353 |     obj2 = Object.new
354 |     @subject.refute_same obj1, obj2
355 |     should_fail(/Expected # \(oid=#{obj1.object_id}\) to not be the same as # \(oid=#{obj1.object_id}\)/) do
356 |       @subject.refute_same obj1, obj1
357 |     end
358 |   end
359 | 
360 |   def test_assert_silent
361 |     @subject.assert_silent {}
362 |     msg = <<~MSG.chomp
363 |       In stdout
364 |       Differing strings.
365 | 
366 |       Expected: ""
367 |         Actual: "foo\\n"
368 |     MSG
369 |     should_fail msg do
370 |       @subject.assert_silent {
371 |         puts "foo"
372 |       }
373 |     end
374 |   end
375 | 
376 |   def test_assert_throws
377 |     @subject.assert_throws :foo do
378 |       throw :foo
379 |     end
380 |     should_fail "Expected :bar to have been thrown, not :baz" do
381 |       @subject.assert_throws :bar do
382 |         throw :baz
383 |       end
384 |     end
385 |   end
386 | 
387 |   def test_doesnt_call_message_procs_on_success
388 |     @subject.assert_nil nil, proc { raise "Shouldn't be called" }
389 |   end
390 | end
391 | 
--------------------------------------------------------------------------------
/tests/tldr/minitest_compatibility_test.rb:
--------------------------------------------------------------------------------
 1 | require_relative "../test_helper"
 2 | 
 3 | class MinitestCompatibilityTest < AssertionTestCase
 4 |   class Compatty
 5 |     include TLDR::Assertions
 6 |     include TLDR::MinitestCompatibility
 7 |   end
 8 | 
 9 |   def setup
10 |     super
11 |     @subject = Compatty.new
12 |   end
13 | 
14 |   def test_capture_io
15 |     out, err = @subject.capture_io do
16 |       puts "out"
17 |       warn "err"
18 |     end
19 | 
20 |     assert_equal "out\n", out
21 |     assert_equal "err\n", err
22 |   end
23 | 
24 |   def test_mu_pp
25 |     assert_equal "\"foo\"", @subject.mu_pp("foo")
26 |     assert_includes @subject.mu_pp("Hello, 世界!".encode("UTF-32LE")), <<~MSG.chomp
27 |       # encoding: UTF-32LE
28 |       #    valid: true
29 |       "Hello,
30 |     MSG
31 |   end
32 | 
33 |   class CompatibleTldr < TLDR
34 |     include TLDR::MinitestCompatibility
35 |   end
36 | 
37 |   def test_tldr_doesnt_define_minitest_compatibility_methods_by_default
38 |     refute_respond_to TLDR.new, :capture_io
39 |     refute_respond_to TLDR.new, :mu_pp
40 |     assert_respond_to CompatibleTldr.new, :capture_io
41 |     assert_respond_to CompatibleTldr.new, :mu_pp
42 |   end
43 | end
44 | 
--------------------------------------------------------------------------------
/tests/tldr/reporters/default_test.rb:
--------------------------------------------------------------------------------
  1 | require_relative "../../test_helper"
  2 | 
  3 | class DefaultTest < Minitest::Test
  4 |   class SomeTest < TLDR
  5 |     def test_a
  6 |     end
  7 | 
  8 |     def test_b
  9 |     end
 10 | 
 11 |     def test_c
 12 |     end
 13 |   end
 14 | 
 15 |   def setup
 16 |     @io = SillyIO.new
 17 |   end
 18 | 
 19 |   def test_parallel_output
 20 |     fake_test_results!(TLDR::Config.new(seed: 42))
 21 | 
 22 |     @subject.before_suite([@test_a])
 23 |     assert_equal <<~MSG, @io.string
 24 |       Command: #{"bundle exec " if defined?(Bundler)}tldr --seed 42
 25 |       --seed 42
 26 | 
 27 |       Running:
 28 | 
 29 |     MSG
 30 |     @io.clear
 31 | 
 32 |     @subject.after_test(@test_a_result)
 33 |     assert_equal ".", @io.string
 34 |     @io.clear
 35 | 
 36 |     @subject.after_tldr([@test_a, @test_b, @test_c], [@test_b_wip], [@test_a_result])
 37 |     assert_equal <<~MSG, scrub_time(@io.string)
 38 |       !
 39 | 
 40 |       ==================== ABORTED RUN ====================
 41 | 
 42 |       too long; didn't run!
 43 | 
 44 |       Completed 1 of 3 tests (33%) before running out of time.
 45 | 
 46 |       1 test was cancelled in progress:
 47 |         XXXms - DefaultTest::SomeTest#test_b [tests/tldr/reporters/default_test.rb:8]
 48 | 
 49 |       Your 1 slowest completed tests:
 50 |         XXXms - DefaultTest::SomeTest#test_a [tests/tldr/reporters/default_test.rb:5]
 51 | 
 52 |       Run the 2 tests that didn't finish:
 53 |         #{"bundle exec " if defined?(Bundler)}tldr --seed 42 "tests/tldr/reporters/default_test.rb:8:11"
 54 | 
 55 | 
 56 |       Suppress this summary with --yes-i-know
 57 | 
 58 |       ==================== ABORTED RUN ====================
 59 | 
 60 |       Finished in XXXms.
 61 | 
 62 |       1 test class, 1 test method, 0 failures, 0 errors, 0 skips
 63 |     MSG
 64 |   end
 65 | 
 66 |   def test_parallel_output_with_backtraces_and_emoji
 67 |     fake_test_results!(TLDR::Config.new(seed: 42, emoji: true, print_interrupted_test_backtraces: true))
 68 |     @subject.before_suite([@test_a])
 69 |     @subject.after_test(@test_a_result)
 70 |     @io.clear
 71 | 
 72 |     @subject.after_tldr([@test_a, @test_b, @test_c], [@test_b_wip], [@test_a_result])
 73 | 
 74 |     assert_equal <<~MSG, scrub_time(@io.string)
 75 |       🥵
 76 | 
 77 |       🚨==================== ABORTED RUN ====================🚨
 78 | 
 79 |       too long; didn't run!
 80 | 
 81 |       🏃 Completed 1 of 3 tests (33%) before running out of time.
 82 | 
 83 |       🙅 1 test was cancelled in progress:
 84 |         XXXms - DefaultTest::SomeTest#test_b [tests/tldr/reporters/default_test.rb:8]
 85 |           Backtrace at the point of cancellation:
 86 |           /path/to/lib/a.rb:in `a'
 87 |           /path/to/lib/b.rb:in `b'
 88 |           /path/to/lib/c.rb:in `c'
 89 | 
 90 |       🐢 Your 1 slowest completed tests:
 91 |         XXXms - DefaultTest::SomeTest#test_a [tests/tldr/reporters/default_test.rb:5]
 92 | 
 93 |       🤘 Run the 2 tests that didn't finish:
 94 |         #{"bundle exec " if defined?(Bundler)}tldr --seed 42 --emoji --print-interrupted-test-backtraces "tests/tldr/reporters/default_test.rb:8:11"
 95 | 
 96 | 
 97 |       🙈 Suppress this summary with --yes-i-know
 98 | 
 99 |       🚨==================== ABORTED RUN ====================🚨
100 | 
101 |       Finished in XXXms.
102 | 
103 |       1 test class, 1 test method, 0 failures, 0 errors, 0 skips
104 |     MSG
105 |   end
106 | 
107 |   def test_parallel_output_with_squelched_explainer
108 |     fake_test_results!(TLDR::Config.new(seed: 42, emoji: true, print_interrupted_test_backtraces: true, yes_i_know: true))
109 | 
110 |     @subject.before_suite([@test_a])
111 |     @subject.after_test(@test_a_result)
112 |     @io.clear
113 | 
114 |     @subject.after_tldr([@test_a, @test_b, @test_c], [@test_b_wip], [@test_a_result])
115 | 
116 |     assert_equal <<~MSG, scrub_time(@io.string)
117 |       🥵
118 | 
119 |       🚨 TLDR after completing 1 of 3 tests! Print full summary by omitting --yes-i-know
120 | 
121 |       Finished in XXXms.
122 | 
123 |       1 test class, 1 test method, 0 failures, 0 errors, 0 skips
124 |     MSG
125 |   end
126 | 
127 |   private
128 | 
129 |   def fake_test_results!(config)
130 |     @subject = TLDR::Reporters::Default.new(config, @io, @io)
131 |     @test_a = TLDR::Test.new(SomeTest, :test_a)
132 |     @test_b = TLDR::Test.new(SomeTest, :test_b)
133 |     @test_c = TLDR::Test.new(SomeTest, :test_c)
134 |     @test_a_result = TLDR::TestResult.new(@test_a, nil, 500)
135 |     @test_b_wip = TLDR::WIPTest.new(@test_b, Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond) - 1_800_000)
136 |     @test_b_wip.instance_variable_set(:@backtrace_at_exit, ["/path/to/lib/a.rb:in `a'", "/path/to/lib/b.rb:in `b'", "/path/to/lib/c.rb:in `c'"])
137 |   end
138 | 
139 |   def scrub_time string
140 |     string.gsub(/(\d+)ms/, "XXXms")
141 |   end
142 | 
143 |   class SillyIO < StringIO
144 |     def clear
145 |       truncate(0)
146 |       rewind
147 |     end
148 |   end
149 | end
150 | 
--------------------------------------------------------------------------------
/tests/tldr/strategizer_test.rb:
--------------------------------------------------------------------------------
  1 | require_relative "../test_helper"
  2 | 
  3 | class TLDR
  4 |   class StrategizerTest < Minitest::Test
  5 |     class TA < TLDR
  6 |       def test_1
  7 |       end
  8 | 
  9 |       def test_2
 10 |       end
 11 |     end
 12 | 
 13 |     class TB < TLDR
 14 |       def test_1
 15 |       end
 16 | 
 17 |       def test_2
 18 |       end
 19 |     end
 20 | 
 21 |     class TC < TLDR
 22 |       def test_1
 23 |       end
 24 | 
 25 |       def test_2
 26 |       end
 27 |     end
 28 | 
 29 |     def setup
 30 |       @subject = Strategizer.new
 31 |     end
 32 | 
 33 |     def test_no_groups
 34 |       result = @subject.strategize(some_tests, [], [], Config.new(prepend_paths: []))
 35 | 
 36 |       assert result.parallel?
 37 |       assert_equal some_tests, result.parallel_tests_and_groups
 38 |       assert_equal [], result.append_sequential_tests
 39 |     end
 40 | 
 41 |     def test_one_test
 42 |       tests = [Test.new(TA, :test_1)]
 43 | 
 44 |       result = @subject.strategize(tests, [], [], Config.new(prepend_paths: []))
 45 | 
 46 |       refute result.parallel?
 47 |     end
 48 | 
 49 |     def test_parallel_disabled
 50 |       result = @subject.strategize(some_tests, [], [], Config.new(prepend_paths: [], parallel: false))
 51 | 
 52 |       refute result.parallel?
 53 |     end
 54 | 
 55 |     def test_basic_group
 56 |       some_groups = [
 57 |         TestGroup.new([[TB, :test_2], ["TLDR::StrategizerTest::TC", :test_1]])
 58 |       ]
 59 | 
 60 |       result = @subject.strategize(some_tests, some_groups, [], Config.new(prepend_paths: []))
 61 | 
 62 |       assert result.parallel?
 63 |       assert_equal [
 64 |         Test.new(TA, :test_1),
 65 |         Test.new(TA, :test_2),
 66 |         Test.new(TB, :test_1),
 67 |         TestGroup.new([[TB, :test_2], ["TLDR::StrategizerTest::TC", :test_1]]),
 68 |         Test.new(TC, :test_2)
 69 |       ], result.parallel_tests_and_groups
 70 |       assert_equal [
 71 |         Test.new(TB, :test_2),
 72 |         Test.new(TC, :test_1)
 73 |       ], result.parallel_tests_and_groups[3].tests
 74 |     end
 75 | 
 76 |     def test_overlapping_groups_where_a_test_appears_in_multiple_groups
 77 |       some_groups = [
 78 |         TestGroup.new([[TB, :test_2], ["TLDR::StrategizerTest::TC", :test_1]]),
 79 |         TestGroup.new([["TLDR::StrategizerTest::TB", :test_2], [TA, :test_1]])
 80 |       ]
 81 | 
 82 |       result = @subject.strategize(some_tests, some_groups, [], Config.new(prepend_paths: []))
 83 | 
 84 |       assert result.parallel?
 85 |       assert_equal [
 86 |         Test.new(TB, :test_2),
 87 |         Test.new(TA, :test_1),
 88 |         Test.new(TC, :test_1)
 89 |       ], result.parallel_tests_and_groups.first.tests
 90 |       assert_equal some_tests - [
 91 |         Test.new(TA, :test_1),
 92 |         Test.new(TB, :test_2),
 93 |         Test.new(TC, :test_1)
 94 |       ], result.parallel_tests_and_groups[1..]
 95 |     end
 96 | 
 97 |     def test_weird_repetition
 98 |       some_groups = [
 99 |         TestGroup.new([[TA, nil]]),
100 |         TestGroup.new([[TA, nil]]),
101 |         TestGroup.new([[TA, nil], [TB, :test_2]]),
102 |         TestGroup.new([[TA, :test_2], [TC, :test_1]]),
103 |         TestGroup.new([[TB, :test_2], [TC, :test_2]])
104 |       ]
105 |       unsafe_groups = [
106 |         TestGroup.new([[TA, :test_2]]),
107 |         TestGroup.new([[TB, :test_1]])
108 |       ]
109 | 
110 |       result = @subject.strategize(some_tests, some_groups, unsafe_groups, Config.new(prepend_paths: []))
111 | 
112 |       assert result.parallel?
113 |       assert_equal 2, result.parallel_tests_and_groups.size
114 |       assert_equal [
115 |         Test.new(TA, :test_1),
116 |         Test.new(TB, :test_2),
117 |         Test.new(TC, :test_2)
118 |       ], result.parallel_tests_and_groups.first.tests
119 |       assert_equal Test.new(TC, :test_1), result.parallel_tests_and_groups[1]
120 |       assert_equal [
121 |         Test.new(TA, :test_2),
122 |         Test.new(TB, :test_1)
123 |       ], result.append_sequential_tests
124 |     end
125 | 
126 |     def test_append_sequential_tests
127 |       unsafe_groups = [
128 |         TestGroup.new([[TA, nil], [TB, :test_2]])
129 |       ]
130 | 
131 |       result = @subject.strategize(some_tests, [], unsafe_groups, Config.new(prepend_paths: []))
132 | 
133 |       assert result.parallel?
134 |       assert_equal some_tests - [
135 |         Test.new(TA, :test_1),
136 |         Test.new(TA, :test_2),
137 |         Test.new(TB, :test_2)
138 |       ], result.parallel_tests_and_groups
139 |       assert_equal [
140 |         Test.new(TA, :test_1),
141 |         Test.new(TA, :test_2),
142 |         Test.new(TB, :test_2)
143 |       ], result.append_sequential_tests
144 |     end
145 | 
146 |     def test_append_sequential_tests_with_prepend
147 |       unsafe_groups = [
148 |         TestGroup.new([[TA, nil], [TB, :test_2]])
149 |       ]
150 | 
151 |       result = @subject.strategize(some_tests, [], unsafe_groups, Config.new(prepend_paths: ["tests/tldr/strategizer_test.rb:18"]))
152 | 
153 |       assert_equal [Test.new(TB, :test_2)], result.prepend_sequential_tests
154 |       assert_equal some_tests - [
155 |         Test.new(TA, :test_1),
156 |         Test.new(TA, :test_2),
157 |         Test.new(TB, :test_2)
158 |       ], result.parallel_tests_and_groups
159 |       assert_equal [
160 |         Test.new(TA, :test_1),
161 |         Test.new(TA, :test_2)
162 |       ], result.append_sequential_tests
163 |     end
164 | 
165 |     def test_grouped_tests_that_arent_selected_by_the_runner
166 |       some_groups = [
167 |         TestGroup.new([[TA, nil]]),
168 |         TestGroup.new([[TB, :test_1], [TB, :test_2]])
169 |       ]
170 |       unsafe_groups = [
171 |         TestGroup.new([[TC, nil]])
172 |       ]
173 | 
174 |       result = @subject.strategize([
175 |         Test.new(TA, :test_1),
176 |         Test.new(TC, :test_2)
177 |       ], some_groups, unsafe_groups, Config.new(prepend_paths: []))
178 | 
179 |       assert result.parallel?
180 |       assert_equal [
181 |         Test.new(TA, :test_1)
182 |       ], result.parallel_tests_and_groups
183 |       assert_equal [
184 |         Test.new(TC, :test_2)
185 |       ], result.append_sequential_tests
186 |     end
187 | 
188 |     private
189 | 
190 |     def some_tests
191 |       [
192 |         Test.new(TA, :test_1),
193 |         Test.new(TA, :test_2),
194 |         Test.new(TB, :test_1),
195 |         Test.new(TB, :test_2),
196 |         Test.new(TC, :test_1),
197 |         Test.new(TC, :test_2)
198 |       ]
199 |     end
200 |   end
201 | end
202 | 
--------------------------------------------------------------------------------
/tests/tldr/value/config_test.rb:
--------------------------------------------------------------------------------
  1 | require_relative "../../test_helper"
  2 | 
  3 | class ConfigTest < Minitest::Test
  4 |   def test_pre_defaults
  5 |     config = TLDR::Config.new
  6 | 
  7 |     assert_equal "", config.to_full_args
  8 |     assert_equal "\"lol.rb:4\"", config.to_single_path_args("lol.rb:4")
  9 |   end
 10 | 
 11 |   def test_cli_defaults
 12 |     config = TLDR::Config.new(cli_defaults: true)
 13 | 
 14 |     # Won't work unless we change dir to example/a and it'll create pollution
 15 |     # assert_equal ["test/add_test.rb", "test/test_subtract.rb"], config.paths
 16 |     assert_equal ["test/helper.rb"], config.helper_paths
 17 |     assert_equal ["lib", "test"], config.load_paths
 18 |     assert_equal [TLDR::MOST_RECENTLY_MODIFIED_TAG], config.prepend_paths
 19 |     assert config.warnings
 20 |   end
 21 | 
 22 |   def test_non_cli_defaults
 23 |     config = TLDR::Config.new(cli_defaults: false)
 24 | 
 25 |     assert_equal "", config.to_full_args
 26 |     assert_equal [], config.paths
 27 |     assert_equal [], config.helper_paths
 28 |     assert_equal [], config.load_paths
 29 |     assert_equal [], config.prepend_paths
 30 |   end
 31 | 
 32 |   def test_default_no_parallel_when_seed_is_set_explicitly
 33 |     config = TLDR::Config.new(seed: 1234)
 34 | 
 35 |     refute config.parallel
 36 |   end
 37 | 
 38 |   def test_parallel_configurable_when_seed_is_set_explicitly
 39 |     config = TLDR::Config.new
 40 |     config.seed = 1234
 41 |     config.parallel = true
 42 | 
 43 |     assert config.parallel
 44 |   end
 45 | 
 46 |   def test_cli_conversion_with_custom_options
 47 |     config = TLDR::Config.new(
 48 |       seed: 42,
 49 |       verbose: true,
 50 |       print_interrupted_test_backtraces: true,
 51 |       reporter: TLDR::Reporters::Base,
 52 |       helper_paths: ["test_helper.rb"],
 53 |       load_paths: ["app", "lib"],
 54 |       parallel: true,
 55 |       names: ["/test_*/", "test_it"],
 56 |       fail_fast: true,
 57 |       prepend_paths: ["a.rb:3"],
 58 |       paths: ["a.rb:3", "b.rb"],
 59 |       exclude_paths: ["c.rb:4"],
 60 |       exclude_names: ["test_b_1"],
 61 |       warnings: false,
 62 |       yes_i_know: true,
 63 |       i_am_being_watched: true
 64 |     )
 65 | 
 66 |     assert_equal <<~MSG.chomp, config.to_full_args
 67 |       --fail-fast --parallel --seed 42 --name "/test_*/" --name "test_it" --exclude-name "test_b_1" --exclude-path "c.rb:4" --helper "test_helper.rb" --prepend "a.rb:3" --load-path "app" --load-path "lib" --reporter TLDR::Reporters::Base --no-warnings --verbose --yes-i-know --print-interrupted-test-backtraces "a.rb:3" "b.rb"
 68 |     MSG
 69 | 
 70 |     assert_equal <<~MSG.chomp, config.to_single_path_args("lol.rb")
 71 |       --exclude-name "test_b_1" --helper "test_helper.rb" --load-path "app" --load-path "lib" --reporter TLDR::Reporters::Base --no-warnings --verbose --yes-i-know --print-interrupted-test-backtraces "lol.rb"
 72 |     MSG
 73 | 
 74 |     assert_equal <<~MSG.chomp, config.to_full_args(ensure_args: ["--i-am-being-watched"])
 75 |       --fail-fast --parallel --seed 42 --name "/test_*/" --name "test_it" --exclude-name "test_b_1" --exclude-path "c.rb:4" --helper "test_helper.rb" --prepend "a.rb:3" --load-path "app" --load-path "lib" --reporter TLDR::Reporters::Base --no-warnings --verbose --yes-i-know --print-interrupted-test-backtraces "a.rb:3" "b.rb" --i-am-being-watched
 76 |     MSG
 77 |   end
 78 | 
 79 |   def test_cli_conversion_omits_prepend_with_no_prepend
 80 |     config = TLDR::Config.new(
 81 |       seed: 1,
 82 |       no_prepend: true,
 83 |       prepend_paths: ["a.rb:3"]
 84 |     )
 85 | 
 86 |     assert_equal <<~MSG.chomp, config.to_full_args
 87 |       --seed 1 --no-prepend
 88 |     MSG
 89 |   end
 90 | 
 91 |   def test_parallel_logic
 92 |     assert_includes TLDR::Config.new(parallel: true, seed: 1).to_full_args, "--parallel"
 93 |     refute_includes TLDR::Config.new(parallel: true).to_full_args, "--parallel"
 94 |     assert_includes TLDR::Config.new(parallel: false).to_full_args, "--no-parallel"
 95 |     refute_includes TLDR::Config.new(parallel: false, seed: 1).to_full_args, "--parallel"
 96 |   end
 97 | 
 98 |   def test_timeout_arg_printout
 99 |     assert_equal "--timeout", TLDR::Config.new(timeout: 1.8).to_full_args
100 |     assert_equal "--timeout 1.7", TLDR::Config.new(timeout: 1.7).to_full_args
101 |   end
102 | 
103 |   def test_config_path_arg_printout
104 |     assert_equal "--config loljk.yml", TLDR::Config.new(config_path: "loljk.yml").to_full_args
105 |     assert_equal "", TLDR::Config.new(config_path: nil).to_full_args
106 |     assert_equal "--no-config", TLDR::Config.new(config_path: nil, cli_defaults: true).to_full_args
107 |     assert_equal "", TLDR::Config.new(config_path: TLDR::Config::DEFAULT_YAML_PATH, cli_defaults: true).to_full_args
108 |   end
109 | 
110 |   def test_cli_conversion_omits_helper_with_no_helper
111 |     config = TLDR::Config.new(
112 |       seed: 1,
113 |       no_helper: true,
114 |       helper_paths: ["some/silly/helper.rb"]
115 |     )
116 | 
117 |     assert_equal <<~MSG.chomp, config.to_full_args
118 |       --seed 1 --no-helper
119 |     MSG
120 |   end
121 | 
122 |   def test_cli_conversion_cuts_off_prepended_pwd
123 |     config = TLDR::Config.new(
124 |       seed: 1,
125 |       helper_paths: ["#{Dir.pwd}/test_helper.rb"],
126 |       load_paths: ["#{Dir.pwd}/app", "/lol/ok/lib"],
127 |       prepend_paths: ["#{Dir.pwd}/foo.rb"],
128 |       exclude_paths: ["#{Dir.pwd}/bar.rb"],
129 |       paths: ["#{Dir.pwd}/baz.rb"]
130 |     )
131 | 
132 |     assert_equal <<~MSG.chomp, config.to_full_args
133 |       --seed 1 --exclude-path "bar.rb" --helper "test_helper.rb" --prepend "foo.rb" --load-path "app" --load-path "/lol/ok/lib" "baz.rb"
134 |     MSG
135 | 
136 |     assert_equal <<~MSG.chomp, config.to_single_path_args("#{Dir.pwd}/baz.rb")
137 |       --helper "test_helper.rb" --load-path "app" --load-path "/lol/ok/lib" "baz.rb"
138 |     MSG
139 |   end
140 | 
141 |   def test_cli_summary_ignores_prepend_when_it_matches_paths
142 |     config = TLDR::Config.new(
143 |       seed: 1,
144 |       prepend_paths: ["#{Dir.pwd}/foo.rb", "bar.rb"],
145 |       paths: ["#{Dir.pwd}/bar.rb", "foo.rb"]
146 |     )
147 | 
148 |     assert_equal <<~MSG.chomp, config.to_full_args
149 |       --seed 1 "bar.rb" "foo.rb"
150 |     MSG
151 |   end
152 | 
153 |   def test_cli_summary_ignores_no_parallel_when_seed_is_set
154 |     config = TLDR::Config.new(
155 |       seed: 1,
156 |       paths: ["foo.rb"]
157 |     )
158 | 
159 |     assert_equal <<~MSG.chomp, config.to_full_args
160 |       --seed 1 "foo.rb"
161 |     MSG
162 |   end
163 | 
164 |   def test_merging_configs_basic
165 |     config = TLDR::Config.new(emoji: true, prepend_paths: ["a.rb:1"], paths: ["a.rb"])
166 |     other = TLDR::Config.new(emoji: false, seed: 1, prepend_paths: ["a.rb:2"], config_intended_for_merge_only: true)
167 | 
168 |     result = config.merge(other)
169 | 
170 |     refute_same result, config
171 |     refute result.config_intended_for_merge_only
172 |     # Seed logic still works, it was just resolved by the otherconfig
173 |     assert result.seed_set_intentionally
174 |     assert_equal 1, result.seed
175 |     refute result.parallel
176 |     # Basic merging happens
177 |     assert_equal ["a.rb"], result.paths
178 |     assert_equal ["a.rb:2"], result.prepend_paths
179 |     refute result.emoji
180 |   end
181 | end
182 | 
--------------------------------------------------------------------------------
/tests/tldr/value/test_test.rb:
--------------------------------------------------------------------------------
 1 | require_relative "../../test_helper"
 2 | 
 3 | class FakeTest < TLDR
 4 |   def test_something
 5 |     assert_equal 1, 2
 6 |   end
 7 | end
 8 | 
 9 | class TestTest < Minitest::Test
10 |   def test_end_line
11 |     test = TLDR::Test.new(FakeTest, :test_something)
12 |     refute test.covers_line?(3)
13 |     assert test.covers_line?(4)
14 |     assert test.covers_line?(5)
15 |     assert test.covers_line?(6)
16 |     refute test.covers_line?(7)
17 |   end
18 | end
19 | 
--------------------------------------------------------------------------------
/tests/tldr/yaml_parser_test.rb:
--------------------------------------------------------------------------------
 1 | require_relative "../test_helper"
 2 | 
 3 | class YamlParserTest < Minitest::Test
 4 |   def setup
 5 |     @subject = TLDR::YamlParser.new
 6 |   end
 7 | 
 8 |   def test_yaml_file_paths_globbing
 9 |     yaml = <<~YAML
10 |       paths:
11 |         - lib/tldr/yaml_*.rb
12 |         - "tests/tldr/yaml_parser_test.rb"
13 |     YAML
14 |     with_temp_file "foo.yml", yaml do |yaml_path|
15 |       assert_equal({paths: [
16 |         "lib/tldr/yaml_parser.rb",
17 |         "tests/tldr/yaml_parser_test.rb"
18 |       ]}, @subject.parse(yaml_path))
19 |     end
20 |   end
21 | 
22 |   class FauxReporter
23 |   end
24 | 
25 |   def test_reporter_lookup
26 |     with_temp_file "foo.yml", "reporter: YamlParserTest::FauxReporter" do |yaml_path|
27 |       assert_equal({reporter: "YamlParserTest::FauxReporter"}, @subject.parse(yaml_path))
28 |     end
29 |   end
30 | 
31 |   def test_yaml_file_translating_timeout_values
32 |     with_temp_file "foo.yml", "timeout: true" do |yaml_path|
33 |       assert_equal({timeout: TLDR::Config::DEFAULT_TIMEOUT}, @subject.parse(yaml_path))
34 |     end
35 | 
36 |     with_temp_file "foo.yml", "timeout: false" do |yaml_path|
37 |       assert_equal({timeout: -1}, @subject.parse(yaml_path))
38 |     end
39 | 
40 |     with_temp_file "foo.yml", "timeout: null" do |yaml_path|
41 |       assert_equal({timeout: nil}, @subject.parse(yaml_path))
42 |     end
43 | 
44 |     with_temp_file "foo.yml", "timeout: 42.3" do |yaml_path|
45 |       assert_equal({timeout: 42.3}, @subject.parse(yaml_path))
46 |     end
47 | 
48 |     with_temp_file "foo.yml", "timeout: '42.3'" do |yaml_path|
49 |       assert_equal({timeout: 42.3}, @subject.parse(yaml_path))
50 |     end
51 |   end
52 | 
53 |   def test_bs_args_error
54 |     with_temp_file ".sure_jan.yml", "boyfriend: George Glass" do |yaml_path|
55 |       e = assert_raises(TLDR::Error) do
56 |         @subject.parse(yaml_path)
57 |       end
58 |       assert_equal "Invalid keys in .sure_jan.yml file: boyfriend", e.message
59 |     end
60 |   end
61 | 
62 |   def test_empty_file
63 |     with_temp_file "foo.yml", "" do |yaml_path|
64 |       assert_equal({}, @subject.parse(yaml_path))
65 |     end
66 |   end
67 | end
68 | 
--------------------------------------------------------------------------------
/tests/tldr_test.rb:
--------------------------------------------------------------------------------
1 | require_relative "test_helper"
2 | 
3 | class TLDRTest < Minitest::Test
4 |   def test_that_it_has_a_version_number
5 |     refute_nil ::TLDR::VERSION
6 |   end
7 | end
8 | 
--------------------------------------------------------------------------------
/tests/warnings_test.rb:
--------------------------------------------------------------------------------
 1 | require_relative "test_helper"
 2 | 
 3 | class WarningsTest < Minitest::Test
 4 |   def test_warnings
 5 |     result = TLDRunner.should_succeed "warnings.rb"
 6 | 
 7 |     assert_includes result.stderr, "warning: method redefined; discarding old test_warning"
 8 |   end
 9 | 
10 |   def test_no_warnings
11 |     result = TLDRunner.should_succeed "warnings.rb", "--no-warnings"
12 | 
13 |     refute_includes result.stderr, "warning: method redefined; discarding old test_warning"
14 |   end
15 | end
16 | 
--------------------------------------------------------------------------------
/tests/wip_test_test.rb:
--------------------------------------------------------------------------------
 1 | require_relative "test_helper"
 2 | 
 3 | class WipTestTest < Minitest::Test
 4 |   class SomeTest < TLDR
 5 |     def test_a
 6 |     end
 7 |   end
 8 | 
 9 |   def test_backtrace_at_exit
10 |     test_a = TLDR::Test.new(SomeTest, :test_a)
11 |     wip_test = TLDR::WIPTest.new(test_a, Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond) - 1_800_000)
12 | 
13 |     test_thread = Thread.new(name: "test_thread") {
14 |       wip_test.thread = Thread.current
15 |       loop { sleep 0.001 }
16 |     }
17 |     sleep 0.001 until wip_test.thread
18 | 
19 |     assert_nil wip_test.backtrace_at_exit
20 | 
21 |     wip_test.capture_backtrace_at_exit
22 |     test_thread.exit
23 | 
24 |     if TLDR::RubyUtil.version >= "3.4"
25 |       assert_match("wip_test_test.rb:15:in 'Kernel#sleep'", wip_test.backtrace_at_exit[0])
26 |       assert_match(":in 'block (2 levels) in WipTestTest#test_backtrace_at_exit'", wip_test.backtrace_at_exit[1])
27 |       assert_match(":in 'Kernel#loop'", wip_test.backtrace_at_exit[2])
28 |       assert_match(":in 'block in WipTestTest#test_backtrace_at_exit'", wip_test.backtrace_at_exit[3])
29 |     else
30 |       assert_match("wip_test_test.rb:15:in `sleep'", wip_test.backtrace_at_exit[0])
31 |       assert_match(":in `block (2 levels) in test_backtrace_at_exit'", wip_test.backtrace_at_exit[1])
32 |       assert_match(":in `loop'", wip_test.backtrace_at_exit[2])
33 |       assert_match(":in `block in test_backtrace_at_exit'", wip_test.backtrace_at_exit[3])
34 |     end
35 |   end
36 | 
37 |   def test_backtrace_at_exit_without_thread
38 |     test_a = TLDR::Test.new(SomeTest, :test_a)
39 |     wip_test = TLDR::WIPTest.new(test_a, Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond) - 1_800_000)
40 | 
41 |     wip_test.capture_backtrace_at_exit
42 | 
43 |     assert_nil wip_test.backtrace_at_exit
44 |   end
45 | end
46 | 
--------------------------------------------------------------------------------
/tldr.gemspec:
--------------------------------------------------------------------------------
 1 | require_relative "lib/tldr/version"
 2 | 
 3 | Gem::Specification.new do |spec|
 4 |   spec.name = "tldr"
 5 |   spec.version = TLDR::VERSION
 6 |   spec.authors = ["Justin Searls", "Aaron Patterson"]
 7 |   spec.email = ["searls@gmail.com", "tenderlove@ruby-lang.org"]
 8 | 
 9 |   spec.summary = "TLDR will run your tests, but only for 1.8 seconds."
10 |   spec.homepage = "https://github.com/tendersearls/tldr"
11 |   spec.license = "MIT"
12 |   spec.required_ruby_version = ">= 3.1"
13 | 
14 |   spec.metadata["homepage_uri"] = spec.homepage
15 |   spec.metadata["source_code_uri"] = spec.homepage
16 |   spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
17 | 
18 |   # Specify which files should be added to the gem when it is released.
19 |   # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
20 |   spec.files = Dir.chdir(__dir__) do
21 |     `git ls-files -z`.split("\x0").reject do |f|
22 |       (File.expand_path(f) == __FILE__) ||
23 |         f.start_with?(*%w[bin/ tests/ example/ spec/ features/ .git .circleci appveyor Gemfile])
24 |     end
25 |   end
26 |   spec.bindir = "exe"
27 |   spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
28 |   spec.require_paths = ["lib"]
29 | 
30 |   spec.add_dependency "super_diff", "~> 0.10"
31 |   spec.add_dependency "concurrent-ruby", "~> 1.2"
32 |   spec.add_dependency "irb", "~> 1.10"
33 | end
34 | 
--------------------------------------------------------------------------------