├── .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.0.0]
4 |
5 | * **BREAKING** you know how the whole point of TLDR is that it aborts your test
6 | run after 1.8s? Yeah, well, it doesn't anymore. Use `--timeout` to enable it
7 | * **BREAKING** replace the `--no-dotfile` flag and has been replaced by a
8 | `--[no-]config PATH` flag. To skip loading the YAML file, use `--no-config`.
9 | To set the file, use `--config FILE` option
10 | * **BREAKING** Remove `assert_include?` and `refute_include?` in favor of
11 | Minitest-compatible `assert_includes` and `refute_includes`.
12 | * **BREAKING** Rename `TLDR::Assertions::MinitestCompatibility` to `TLDR::MinitestCompatibility` and remove `assert_send`, which [nobody uses](https://github.com/minitest/minitest/issues/668)
13 | * **BREAKING** Replace `no_emoji` YAML option with `emoji` option. Disable emoji output by default. Add `--emoji` flag for enabling it.
14 | * Add `--[no-]timeout TIMEOUT` flag and `timeout` YAML option. To enable the
15 | TLDR Classic™ default of 1.8s, specify `--timeout` from the CLI or `timeout: true`
16 | in YAML. To specify a custom timeout of 42.3 seconds, flag `--timeout 42.3` or
17 | `timeout: 42.3` in YAML
18 | * Add `require "tldr/autorun"`, which adds an `at_exit` hook so that tests can be
19 | 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))
20 | * Fix custom reporters by looking them up only after helpers have a chance to run. [#15](https://github.com/tendersearls/tldr/issues/15)
21 |
22 | ## [0.10.1]
23 |
24 | * Fix 3.4 / Prism [#17](https://github.com/tendersearls/tldr/pull/17)
25 |
26 | ## [0.10.0]
27 |
28 | * 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)
29 |
30 | ## [0.9.5]
31 |
32 | * Fix warning when defining `setup`/`teardown` on TLDR class itself [#7](https://github.com/tendersearls/tldr/issues/7)
33 |
34 | ## [0.9.4]
35 |
36 | * Fix Sorbet compatibility [#5](https://github.com/tendersearls/tldr/issues/5)
37 |
38 | ## [0.9.3]
39 |
40 | * Print how many tests ran vs. didn't even when suppressing TLDR summary
41 |
42 | ## [0.9.2]
43 |
44 | * Don't redundantly print out dotfile config values for re-run instructions
45 |
46 | ## [0.9.1]
47 |
48 | * Correctly clear the screen between runs
49 |
50 | ## [0.9.0]
51 |
52 | * Add a `--watch` option that will spawn fswatch | xargs and clear the screen
53 | between runs (requires fswatch to gbe installed)
54 | * Add "lib" as a default load path along with "test"
55 |
56 | ## [0.8.0]
57 |
58 | * Add a `--yes-i-know` flag that will suppress the large warning when your test
59 | suite runs over the 1.8s limit
60 |
61 | ## [0.7.0]
62 |
63 | * Add a `tldt` alias for folks who have another executable named `tldr` on their
64 | paths
65 | * BREAKING: Reverse decision in 0.1.1 to capture_io on every TLDR subclass;
66 | moving back to the MinitestCompatibility mixin
67 | * Fix `assert_in_delta` defaultarg to retain Minitest compatibility
68 | * Add `mu_pp` to the MinitestCompatibility mixin
69 |
70 | ## [0.6.2]
71 |
72 | * Since TLDR runs fine on 3.1, reduce the gem requirement
73 |
74 | ## [0.6.1]
75 |
76 | * Correctly report the number of test classes that run
77 | * Finish planning the test run before starting the clock on the timer (that's
78 | a millisecond or two in savings!)
79 |
80 | ## [0.6.0]
81 |
82 | * When `dont_run_these_in_parallel!` and `run_these_together!` are called from a
83 | super class, gather subclasses' methods as well when the method is `nil`
84 | * Stop shelling out to `tldr` from our Rake task. Rescue `SystemExit` instead
85 | * Rename `Config#helper` to `Config#helper_paths`, which YAML config keys
86 | * Print Ruby warnings by default (disable with --no-warnings)
87 |
88 | ## [0.5.0]
89 |
90 | * Define your own Rake tasks with `TLDR::Task` and pass in a custom configuration
91 | * Any tests with `--prepend` AND marked thread-unsafe with `dont_run_these_in_parallel`
92 | will be run BEFORE the parallel tests start running. This way if you're working
93 | on a non-parallelizable test, running `tldr` will automatically run it first
94 | thing
95 | * Stop printing `--seed` in run commands, since it can be confusing to discover
96 | that will disable `--parallel`. Instead, print the seed option beneath
97 |
98 | ## [0.4.0]
99 |
100 | * Add `TLDR.dont_run_these_in_parallel!` method to allow tests to indicate that they
101 | must be run in isolation and not concurrently with any other tests
102 | * Add support for `around` hooks (similar to [minitest-around](https://github.com/splattael/minitest-around))
103 |
104 | ## [0.3.0]
105 |
106 | * Add `TLDR.run_these_together!` method to allow tests that can't safely be run
107 | concurrently to be grouped and run together
108 |
109 | ## [0.2.1]
110 |
111 | * Define a default empty setup/teardown in the base class, to guard against
112 | users getting `super: no superclass method `setup'` errors when they dutifully
113 | call super from their hooks
114 |
115 | ## [0.2.0]
116 |
117 | - Add a rake task "tldr"
118 | ## [0.1.1]
119 |
120 | - Improve Minitest compatibility by mixing in Assertions#capture_io
121 | - Fix whitespace in reporter
122 |
123 | ## [0.1.0]
124 |
125 | - Initial release
126 |
--------------------------------------------------------------------------------
/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.0.0)
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 | concurrent-ruby (1.3.5)
15 | date (3.4.1)
16 | diff-lcs (1.6.1)
17 | io-console (0.8.0)
18 | irb (1.15.1)
19 | pp (>= 0.6.0)
20 | rdoc (>= 4.0.0)
21 | reline (>= 0.4.2)
22 | json (2.10.2)
23 | language_server-protocol (3.17.0.4)
24 | lint_roller (1.1.0)
25 | m (1.6.2)
26 | method_source (>= 0.6.7)
27 | rake (>= 0.9.2.2)
28 | method_source (1.1.0)
29 | minitest (5.25.5)
30 | optimist (3.2.1)
31 | parallel (1.26.3)
32 | parser (3.3.7.2)
33 | ast (~> 2.4.1)
34 | racc
35 | patience_diff (1.2.0)
36 | optimist (~> 3.0)
37 | pp (0.6.2)
38 | prettyprint
39 | prettyprint (0.2.0)
40 | prism (1.4.0)
41 | psych (5.2.3)
42 | date
43 | stringio
44 | racc (1.8.1)
45 | rainbow (3.1.1)
46 | rake (13.2.1)
47 | rdoc (6.13.1)
48 | psych (>= 4.0.0)
49 | regexp_parser (2.10.0)
50 | reline (0.6.0)
51 | io-console (~> 0.5)
52 | rubocop (1.73.2)
53 | json (~> 2.3)
54 | language_server-protocol (~> 3.17.0.2)
55 | lint_roller (~> 1.1.0)
56 | parallel (~> 1.10)
57 | parser (>= 3.3.0.2)
58 | rainbow (>= 2.2.2, < 4.0)
59 | regexp_parser (>= 2.9.3, < 3.0)
60 | rubocop-ast (>= 1.38.0, < 2.0)
61 | ruby-progressbar (~> 1.7)
62 | unicode-display_width (>= 2.4.0, < 4.0)
63 | rubocop-ast (1.43.0)
64 | parser (>= 3.3.7.2)
65 | prism (~> 1.4)
66 | rubocop-performance (1.24.0)
67 | lint_roller (~> 1.1)
68 | rubocop (>= 1.72.1, < 2.0)
69 | rubocop-ast (>= 1.38.0, < 2.0)
70 | ruby-progressbar (1.13.0)
71 | standard (1.47.0)
72 | language_server-protocol (~> 3.17.0.2)
73 | lint_roller (~> 1.0)
74 | rubocop (~> 1.73.0)
75 | standard-custom (~> 1.0.0)
76 | standard-performance (~> 1.7)
77 | standard-custom (1.0.2)
78 | lint_roller (~> 1.0)
79 | rubocop (~> 1.50)
80 | standard-performance (1.7.0)
81 | lint_roller (~> 1.1)
82 | rubocop-performance (~> 1.24.0)
83 | stringio (3.1.6)
84 | super_diff (0.15.0)
85 | attr_extras (>= 6.2.4)
86 | diff-lcs
87 | patience_diff
88 | unicode-display_width (3.1.4)
89 | unicode-emoji (~> 4.0, >= 4.0.4)
90 | unicode-emoji (4.0.4)
91 |
92 | PLATFORMS
93 | arm64-darwin-22
94 | arm64-darwin-23
95 | arm64-darwin-24
96 | x86_64-linux
97 |
98 | DEPENDENCIES
99 | m
100 | minitest
101 | rake
102 | standard
103 | tldr!
104 |
105 | BUNDLED WITH
106 | 2.4.17
107 |
--------------------------------------------------------------------------------
/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.0.0)
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 | concurrent-ruby (1.3.5)
14 | date (3.4.1)
15 | diff-lcs (1.6.1)
16 | io-console (0.8.0)
17 | irb (1.15.1)
18 | pp (>= 0.6.0)
19 | rdoc (>= 4.0.0)
20 | reline (>= 0.4.2)
21 | optimist (3.2.1)
22 | patience_diff (1.2.0)
23 | optimist (~> 3.0)
24 | pp (0.6.2)
25 | prettyprint
26 | prettyprint (0.2.0)
27 | psych (5.2.3)
28 | date
29 | stringio
30 | rdoc (6.13.1)
31 | psych (>= 4.0.0)
32 | reline (0.6.0)
33 | io-console (~> 0.5)
34 | stringio (3.1.6)
35 | super_diff (0.15.0)
36 | attr_extras (>= 6.2.4)
37 | diff-lcs
38 | patience_diff
39 |
40 | PLATFORMS
41 | arm64-darwin-22
42 | arm64-darwin-23
43 | arm64-darwin-24
44 |
45 | DEPENDENCIES
46 | tldr!
47 |
48 | BUNDLED WITH
49 | 2.4.17
50 |
--------------------------------------------------------------------------------
/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.0.0)
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 | concurrent-ruby (1.3.5)
14 | date (3.4.1)
15 | diff-lcs (1.6.1)
16 | io-console (0.8.0)
17 | irb (1.15.1)
18 | pp (>= 0.6.0)
19 | rdoc (>= 4.0.0)
20 | reline (>= 0.4.2)
21 | mocktail (2.0.0)
22 | sorbet-eraser (~> 0.3.1)
23 | sorbet-runtime (~> 0.5.9204)
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.3)
31 | date
32 | stringio
33 | rake (13.2.1)
34 | rdoc (6.13.1)
35 | psych (>= 4.0.0)
36 | reline (0.6.0)
37 | io-console (~> 0.5)
38 | sorbet-eraser (0.3.1)
39 | sorbet-runtime (0.5.11954)
40 | stringio (3.1.6)
41 | super_diff (0.15.0)
42 | attr_extras (>= 6.2.4)
43 | diff-lcs
44 | patience_diff
45 |
46 | PLATFORMS
47 | arm64-darwin-22
48 | arm64-darwin-23
49 | arm64-darwin-24
50 |
51 | DEPENDENCIES
52 | mocktail
53 | rake
54 | tldr!
55 |
56 | BUNDLED WITH
57 | 2.4.17
58 |
--------------------------------------------------------------------------------
/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.0.0)
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.0)
14 | concurrent-ruby (1.3.5)
15 | date (3.4.1)
16 | diff-lcs (1.6.1)
17 | erubi (1.13.1)
18 | io-console (0.8.0)
19 | irb (1.15.1)
20 | pp (>= 0.6.0)
21 | rdoc (>= 4.0.0)
22 | reline (>= 0.4.2)
23 | logger (1.6.6)
24 | netrc (0.11.0)
25 | optimist (3.2.1)
26 | parallel (1.26.3)
27 | patience_diff (1.2.0)
28 | optimist (~> 3.0)
29 | pp (0.6.2)
30 | prettyprint
31 | prettyprint (0.2.0)
32 | prism (1.4.0)
33 | psych (5.2.3)
34 | date
35 | stringio
36 | rake (13.2.1)
37 | rbi (0.3.1)
38 | prism (~> 1.0)
39 | rbs (>= 3.4.4)
40 | sorbet-runtime (>= 0.5.9204)
41 | rbs (3.9.1)
42 | logger
43 | rdoc (6.13.1)
44 | psych (>= 4.0.0)
45 | reline (0.6.0)
46 | io-console (~> 0.5)
47 | sorbet (0.5.11954)
48 | sorbet-static (= 0.5.11954)
49 | sorbet-runtime (0.5.11954)
50 | sorbet-static (0.5.11954-universal-darwin)
51 | sorbet-static-and-runtime (0.5.11954)
52 | sorbet (= 0.5.11954)
53 | sorbet-runtime (= 0.5.11954)
54 | spoom (1.6.1)
55 | erubi (>= 1.10.0)
56 | prism (>= 0.28.0)
57 | rbi (>= 0.2.3)
58 | sorbet-static-and-runtime (>= 0.5.10187)
59 | thor (>= 0.19.2)
60 | stringio (3.1.6)
61 | super_diff (0.15.0)
62 | attr_extras (>= 6.2.4)
63 | diff-lcs
64 | patience_diff
65 | tapioca (0.16.11)
66 | benchmark
67 | bundler (>= 2.2.25)
68 | netrc (>= 0.11.0)
69 | parallel (>= 1.21.0)
70 | rbi (~> 0.2)
71 | sorbet-static-and-runtime (>= 0.5.11087)
72 | spoom (>= 1.2.0)
73 | thor (>= 1.2.0)
74 | yard-sorbet
75 | thor (1.3.2)
76 | yard (0.9.37)
77 | yard-sorbet (0.9.0)
78 | sorbet-runtime
79 | yard
80 |
81 | PLATFORMS
82 | arm64-darwin-22
83 | arm64-darwin-23
84 | arm64-darwin-24
85 |
86 | DEPENDENCIES
87 | rake
88 | sorbet
89 | sorbet-runtime
90 | tapioca
91 | tldr!
92 |
93 | BUNDLED WITH
94 | 2.4.17
95 |
--------------------------------------------------------------------------------
/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.0.0)
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 | concurrent-ruby (1.3.5)
14 | date (3.4.1)
15 | diff-lcs (1.6.1)
16 | io-console (0.8.0)
17 | irb (1.15.1)
18 | pp (>= 0.6.0)
19 | rdoc (>= 4.0.0)
20 | reline (>= 0.4.2)
21 | optimist (3.2.1)
22 | patience_diff (1.2.0)
23 | optimist (~> 3.0)
24 | pp (0.6.2)
25 | prettyprint
26 | prettyprint (0.2.0)
27 | psych (5.2.3)
28 | date
29 | stringio
30 | rdoc (6.13.1)
31 | psych (>= 4.0.0)
32 | reline (0.6.0)
33 | io-console (~> 0.5)
34 | stringio (3.1.6)
35 | super_diff (0.15.0)
36 | attr_extras (>= 6.2.4)
37 | diff-lcs
38 | patience_diff
39 |
40 | PLATFORMS
41 | arm64-darwin-22
42 | arm64-darwin-23
43 | arm64-darwin-24
44 |
45 | DEPENDENCIES
46 | tldr!
47 |
48 | BUNDLED WITH
49 | 2.4.17
50 |
--------------------------------------------------------------------------------
/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[:print_interrupted_test_backtraces], "Print stack traces of tests interrupted after a timeout" do |print_interrupted_test_backtraces|
110 | options[:print_interrupted_test_backtraces] = print_interrupted_test_backtraces
111 | end
112 |
113 | unless ARGV.include?("--help") || ARGV.include?("--h")
114 | opts.on CONFLAGS[:i_am_being_watched], "[INTERNAL] Signals to tldr it is being invoked under --watch mode" do
115 | options[:i_am_being_watched] = true
116 | end
117 |
118 | opts.on "--comment COMMENT", String, "[INTERNAL] No-op; used for multi-line execution instructions" do
119 | # See "--comment" in lib/tldr/reporters/default.rb for an example of how this is used internally
120 | end
121 | end
122 | end.parse!(args)
123 |
124 | options[:paths] = args if args.any?
125 | options[:config_path] = case options[:config_path]
126 | when nil then Config::DEFAULT_YAML_PATH
127 | when false then nil
128 | else options[:config_path]
129 | end
130 |
131 | Config.new(**options)
132 | end
133 |
134 | private
135 |
136 | def handle_unparsable_optional_value(og_args, option_name, value, &blk)
137 | yield
138 | rescue ArgumentError
139 | args = og_args.dup
140 | if (option_index = args.index("--#{option_name}"))
141 | args.insert(option_index + 1, "--")
142 | warn <<~MSG
143 | TLDR exited in error!
144 |
145 | We couldn't parse #{value.inspect} as a valid #{option_name} value
146 |
147 | Did you mean to set --#{option_name} as the last option and without an explicit value?
148 |
149 | If so, you need to append ' -- ' before any paths, like:
150 |
151 | tldr #{args.join(" ")}
152 | MSG
153 | exit 1
154 | else
155 | raise
156 | end
157 | end
158 | end
159 | end
160 |
--------------------------------------------------------------------------------
/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 | exit!(3)
44 | end
45 |
46 | sleep(config.timeout)
47 |
48 | # Don't hard-kill the runner if user is debugging, it'll
49 | # screw up their terminal slash be a bad time
50 | if IRB.CurrentContext
51 | IRB.conf[:AT_EXIT] << explode
52 | else
53 | explode.call
54 | end
55 | }
56 |
57 | results = @executor.execute(plan) { |test|
58 | run_test(test, config, plan, reporter)
59 | }.tap do
60 | time_bomb.kill
61 | end
62 |
63 | unless @run_aborted.true?
64 | reporter.after_suite(results) if reporter.respond_to?(:after_suite)
65 | exit(exit_code(results))
66 | end
67 | end
68 |
69 | private
70 |
71 | def run_test test, config, plan, reporter
72 | e = nil
73 | start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
74 | wip_test = WIPTest.new(test, start_time, Thread.current)
75 | @wip << wip_test
76 | runtime = time_it(start_time) do
77 | instance = test.test_class.new
78 | instance.setup if instance.respond_to?(:setup)
79 | if instance.respond_to?(:around)
80 | did_run = false
81 | instance.around {
82 | did_run = true
83 | instance.send(test.method_name)
84 | }
85 | raise Error, "#{test.test_class}#around failed to yield or call the passed test block" unless did_run
86 | else
87 | instance.send(test.method_name)
88 | end
89 | instance.teardown if instance.respond_to?(:teardown)
90 | rescue Skip, Failure, StandardError => e
91 | end
92 | TestResult.new(test, e, runtime).tap do |result|
93 | next if @run_aborted.true?
94 | @results << result
95 | @wip.delete(wip_test)
96 | reporter.after_test(result) if reporter.respond_to?(:after_test)
97 | fail_fast(reporter, plan, result) if result.failing? && config.fail_fast
98 | end
99 | end
100 |
101 | def fail_fast reporter, plan, fast_failed_result
102 | unless @run_aborted.true?
103 | @run_aborted.make_true
104 | abort = proc do
105 | reporter.after_fail_fast(plan.tests, @wip.dup, @results.dup, fast_failed_result) if reporter.respond_to?(:after_fail_fast)
106 | exit!(exit_code([fast_failed_result]))
107 | end
108 |
109 | if IRB.CurrentContext
110 | IRB.conf[:AT_EXIT] << abort
111 | else
112 | abort.call
113 | end
114 | end
115 | end
116 |
117 | def time_it start
118 | yield
119 | ((Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond) - start) / 1000.0).round
120 | end
121 |
122 | def exit_code results
123 | if results.any? { |result| result.error? }
124 | 2
125 | elsif results.any? { |result| result.failure? }
126 | 1
127 | else
128 | 0
129 | end
130 | end
131 | end
132 | end
133 |
--------------------------------------------------------------------------------
/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 | paths: nil
26 | }.freeze
27 |
28 | PATH_FLAGS = [:paths, :helper_paths, :load_paths, :prepend_paths, :exclude_paths].freeze
29 | MOST_RECENTLY_MODIFIED_TAG = "MOST_RECENTLY_MODIFIED".freeze
30 | CONFIG_ATTRIBUTES = [
31 | :timeout, :watch, :fail_fast, :parallel, :seed, :names, :exclude_names,
32 | :exclude_paths, :helper_paths, :no_helper, :prepend_paths, :no_prepend,
33 | :load_paths, :base_path, :config_path, :reporter, :emoji, :warnings,
34 | :verbose, :yes_i_know, :print_interrupted_test_backtraces,
35 | :i_am_being_watched, :paths,
36 | # Internal properties
37 | :config_intended_for_merge_only, :seed_set_intentionally, :cli_defaults
38 | ].freeze
39 |
40 | Config = Struct.new(*CONFIG_ATTRIBUTES, keyword_init: true) do
41 | def initialize(**args)
42 | @argv_reconstructor = ArgvReconstructor.new
43 |
44 | original_base_path = Dir.pwd
45 | unless args[:config_intended_for_merge_only]
46 | change_working_directory_because_i_am_bad_and_i_should_feel_bad!(args[:base_path])
47 | args = merge_dotfile_args(args) unless args[:config_path].nil?
48 | end
49 | args = undefault_parallel_if_seed_set(args)
50 | unless args[:config_intended_for_merge_only]
51 | args = merge_defaults(args)
52 | revert_working_directory_change_because_itll_ruin_everything!(original_base_path)
53 | end
54 |
55 | super
56 | end
57 |
58 | # These are for internal tracking and resolved at initialization-time
59 | undef_method :config_intended_for_merge_only=, :seed_set_intentionally=,
60 | # These must be set when the Config is first initialized
61 | :cli_defaults=, :config_path=, :base_path=
62 |
63 | def self.build_defaults cli_defaults: true
64 | common = {
65 | timeout: -1,
66 | watch: false,
67 | fail_fast: false,
68 | parallel: true,
69 | seed: rand(10_000),
70 | names: [],
71 | exclude_names: [],
72 | exclude_paths: [],
73 | no_helper: false,
74 | no_prepend: false,
75 | base_path: nil,
76 | reporter: "TLDR::Reporters::Default",
77 | emoji: false,
78 | warnings: true,
79 | verbose: false,
80 | yes_i_know: false,
81 | print_interrupted_test_backtraces: false,
82 | i_am_being_watched: false
83 | }
84 |
85 | if cli_defaults
86 | common.merge(
87 | helper_paths: ["test/helper.rb"],
88 | prepend_paths: [MOST_RECENTLY_MODIFIED_TAG],
89 | load_paths: ["lib", "test"],
90 | config_path: nil,
91 | paths: Dir["test/**/*_test.rb", "test/**/test_*.rb"]
92 | )
93 | else
94 | common.merge(
95 | helper_paths: [],
96 | prepend_paths: [],
97 | load_paths: [],
98 | 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
99 | paths: []
100 | )
101 | end
102 | end
103 |
104 | def undefault_parallel_if_seed_set args
105 | args.merge(
106 | parallel: (args[:parallel].nil? ? args[:seed].nil? : args[:parallel]),
107 | seed_set_intentionally: !args[:seed].nil?
108 | )
109 | end
110 |
111 | def merge_defaults user_args
112 | merged_args = user_args.dup
113 | defaults = Config.build_defaults(cli_defaults: merged_args[:cli_defaults])
114 |
115 | # Arrays
116 | [:names, :exclude_names, :exclude_paths, :helper_paths, :prepend_paths, :load_paths, :paths].each do |key|
117 | merged_args[key] = defaults[key] if merged_args[key].nil? || merged_args[key].empty?
118 | end
119 |
120 | # Booleans
121 | [:watch, :fail_fast, :parallel, :no_helper, :no_prepend, :emoji, :warnings, :verbose, :yes_i_know, :print_interrupted_test_backtraces, :i_am_being_watched].each do |key|
122 | merged_args[key] = defaults[key] if merged_args[key].nil?
123 | end
124 |
125 | # Values
126 | [:timeout, :seed, :base_path, :config_path, :reporter].each do |key|
127 | merged_args[key] ||= defaults[key]
128 | end
129 |
130 | merged_args
131 | end
132 |
133 | def merge other
134 | this_config = to_h
135 | kwargs = this_config.merge(
136 | other.to_h.compact.except(:config_intended_for_merge_only)
137 | )
138 | Config.new(**kwargs)
139 | end
140 |
141 | # We needed this hook (to be called by the planner), because we can't know
142 | # the default prepend location until we have all the resolved test paths,
143 | # so we have to mutate it after the fact.
144 | def update_after_gathering_tests! tests
145 | return unless prepend_paths.include?(MOST_RECENTLY_MODIFIED_TAG)
146 |
147 | self.prepend_paths = prepend_paths.map { |path|
148 | if path == MOST_RECENTLY_MODIFIED_TAG
149 | most_recently_modified_test_file(tests)
150 | else
151 | path
152 | end
153 | }.compact
154 | end
155 |
156 | def to_full_args exclude: [], ensure_args: [], exclude_dotfile_matches: false
157 | @argv_reconstructor.reconstruct(self, exclude:, ensure_args:, exclude_dotfile_matches:)
158 | end
159 |
160 | def to_single_path_args path, exclude_dotfile_matches: false
161 | @argv_reconstructor.reconstruct_single_path_args(self, path, exclude_dotfile_matches:)
162 | end
163 |
164 | def dotfile_args config_path
165 | return {} unless File.exist?(config_path)
166 |
167 | @dotfile_args ||= YamlParser.new.parse(config_path)
168 | end
169 |
170 | private
171 |
172 | def most_recently_modified_test_file tests
173 | return if tests.empty?
174 |
175 | tests.max_by { |test| File.mtime(test.file) }.file
176 | end
177 |
178 | # If the user sets a custom base path, we need to change the working directory
179 | # ASAP, even before globbing to find default paths of tests. If there is
180 | # a way to change all of our Dir.glob calls to be relative to base_path
181 | # without a loss in accuracy, would love to not have to use Dir.chdir!
182 | def change_working_directory_because_i_am_bad_and_i_should_feel_bad! base_path
183 | Dir.chdir(base_path) unless base_path.nil?
184 | end
185 |
186 | def revert_working_directory_change_because_itll_ruin_everything! original_base_path
187 | Dir.chdir(original_base_path) unless Dir.pwd == original_base_path
188 | end
189 |
190 | def merge_dotfile_args args
191 | dotfile_args(args[:config_path]).merge(args)
192 | end
193 | end
194 |
195 | Config::DEFAULT_YAML_PATH = ".tldr.yml"
196 | Config::DEFAULT_TIMEOUT = 1.8
197 | end
198 |
--------------------------------------------------------------------------------
/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 >= line && l <= 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.0.0"
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 | end
59 |
--------------------------------------------------------------------------------
/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 | end
62 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------