├── .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 | --------------------------------------------------------------------------------