loupe 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9c82c36cf7a0a2f22151a36571f568c879b490dd8ffdb1804b00305714059da5
4
+ data.tar.gz: c2f949d723a8df44fab033ba868475b9f8f61405ad7f0ff729c5fb3ee27c8f3a
5
+ SHA512:
6
+ metadata.gz: 1053f8e5eac94340b90915e05686a31854cb54d15914e773eb0a8979a0b64b257e7fd706735654395e88f02e148cc4bb3082f5b380e0165b328fbaba8b88aa22
7
+ data.tar.gz: d6dec0e1c3db33b37cfe4ad5847e6218702e42d93867702d945763b53b45dc925476d34b2103f0f95567a68a43ebcb087f63de22bcf66a54f75038f74cec87d6
data/LICENSE.txt ADDED
@@ -0,0 +1,71 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Vinicius Stock
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
+
23
+
24
+ Includes portions from the Minitest project
25
+
26
+ https://github.com/seattlerb/minitest
27
+ (The MIT License)
28
+
29
+ Copyright © Ryan Davis, seattle.rb
30
+
31
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
32
+ this software and associated documentation files (the 'Software'), to deal in
33
+ the Software without restriction, including without limitation the rights to
34
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
35
+ the Software, and to permit persons to whom the Software is furnished to do so,
36
+ subject to the following conditions:
37
+
38
+ The above copyright notice and this permission notice shall be included in all
39
+ copies or substantial portions of the Software.
40
+
41
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
42
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
43
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
44
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
45
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
46
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
47
+
48
+
49
+ Includes portions from the rspec-expectations project
50
+
51
+ https://github.com/rspec/rspec-expectations The MIT License (MIT)
52
+
53
+ Copyright © 2012 David Chelimsky, Myron Marston Copyright © 2006 David
54
+ Chelimsky, The RSpec Development Team Copyright © 2005 Steven Baker
55
+
56
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
57
+ this software and associated documentation files (the "Software"), to deal in
58
+ the Software without restriction, including without limitation the rights to
59
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
60
+ the Software, and to permit persons to whom the Software is furnished to do so,
61
+ subject to the following conditions:
62
+
63
+ The above copyright notice and this permission notice shall be included in all
64
+ copies or substantial portions of the Software.
65
+
66
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
67
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
68
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
69
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
70
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
71
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,101 @@
1
+ <div align="center">
2
+ <img src="https://user-images.githubusercontent.com/18742907/131183084-82c617d9-b83d-49cd-84f3-447ebb90ca50.png" alt="loupe" height="200px">
3
+ </div>
4
+
5
+ # Loupe
6
+
7
+ [Loupe](https://en.wikipedia.org/wiki/Loupe) is a test framework with built in parallel execution supporting Ractor or forked process modes.
8
+
9
+ ## Installation
10
+
11
+ Add the gem to the `Gemfile`.
12
+
13
+ ```ruby
14
+ gem "loupe"
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ ```shell
20
+ bundle install
21
+ ```
22
+
23
+ Install bundler binstubs in your application.
24
+
25
+ ```shell
26
+ bundle binstub loupe
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ Currently, Loupe only supports writing tests using the test methods syntax. Tests must inherit from `Loupe::Test`, but do not need to explicitly require `test_helper`, like the example below.
32
+
33
+ ```ruby
34
+ # frozen_string_literal: true
35
+
36
+ class MyTest < Loupe::Test
37
+ def before
38
+ @author = Author.create(name: "John")
39
+ @post = Post.create(author: @author)
40
+ end
41
+
42
+ def after
43
+ @author.destroy
44
+ @post.destroy
45
+ end
46
+
47
+ def test_post_is_linked_to_author
48
+ expect(@post.author.name).to_be_equal_to("John")
49
+ end
50
+ end
51
+ ```
52
+
53
+ To run the test suite, invoke the executable using the binstub generated by bundler.
54
+
55
+ ```shell
56
+ bin/loupe test/post_test.rb test/author_test.rb
57
+ ```
58
+
59
+ Tests can run in parallel using Ractor or process mode. When using Ractors, the application's code must be Ractor compatible.
60
+
61
+ ```shell
62
+ bin/loupe --ractor # [default] run tests using Ractor workers
63
+ bin/loupe --process # run tests using forked processes
64
+ bin/loupe --interactive # [default] use an interactive reporter to display test results
65
+ bin/loupe --plain # use a plain reporter to display test results
66
+ bin/loupe --color, --no-color # enable/disable output colors
67
+ bin/loupe --editor=EDITOR # which editor to use for opening files when using interactive mode. The default is the environment variable $EDITOR
68
+ ```
69
+
70
+ To hook Loupe into Rake, use the provided rake task as in the example below.
71
+
72
+ ```ruby
73
+ # Rakefile
74
+
75
+ require "loupe/rake_task"
76
+
77
+ # Instantiate the task and append any desired CLI options
78
+ Loupe::RakeTask.new do |options|
79
+ options << "--plain"
80
+ end
81
+
82
+ # Optionally, set the default task to be test
83
+ task default: :test
84
+ ```
85
+
86
+ Then run
87
+
88
+ ```shell
89
+ bundle exec rake test
90
+ ```
91
+
92
+ ## Credits
93
+
94
+ This project draws a lot of inspiration from other Ruby test frameworks, namely
95
+
96
+ - [Minitest](https://github.com/seattlerb/minitest)
97
+ - [rspec](https://github.com/rspec/rspec)
98
+
99
+ ## Contributing
100
+
101
+ Please refer to the guidelines in [contributing](https://github.com/vinistock/loupe/blob/master/CONTRIBUTING.md).
data/Rakefile ADDED
@@ -0,0 +1,16 @@
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 << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/*_test.rb"]
10
+ end
11
+
12
+ require "rubocop/rake_task"
13
+
14
+ RuboCop::RakeTask.new
15
+
16
+ task default: %i[test rubocop]
data/exe/loupe ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
5
+ $LOAD_PATH.unshift(File.expand_path("#{Dir.pwd}/test", __dir__))
6
+
7
+ require "loupe"
8
+
9
+ Loupe::Cli.new
data/lib/loupe/cli.rb ADDED
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "optparse"
4
+
5
+ module Loupe
6
+ # Cli
7
+ #
8
+ # The Cli class defines all available
9
+ # commands and their options
10
+ class Cli
11
+ # @return [String]
12
+ USAGE = <<~TEXT
13
+ Usage: [test_list] [options]
14
+ TEXT
15
+
16
+ # @return [void]
17
+ def initialize(args = ARGV)
18
+ @options = {
19
+ color: true,
20
+ interactive: true,
21
+ mode: :ractor
22
+ }
23
+
24
+ parse_options(@options, args)
25
+ @options.freeze
26
+
27
+ @files = args
28
+ start
29
+ end
30
+
31
+ private
32
+
33
+ # @param options [Hash<Symbol, BasicObject>]
34
+ # @param args [Array<String>]
35
+ # @return [void]
36
+ def parse_options(options, args) # rubocop:disable Metrics/AbcSize
37
+ OptionParser.new do |opts|
38
+ opts.banner = USAGE
39
+
40
+ opts.on("--version", "Print Loupe's version") do
41
+ warn Loupe::VERSION
42
+ exit(0)
43
+ end
44
+
45
+ opts.on("--color", "--[no-]color", "Enable or disable color in the output") { |value| options[:color] = value }
46
+ opts.on("--interactive", "Use interactive output") { options[:interactive] = true }
47
+ opts.on("--plain", "Use plain non-interactive output") { options[:interactive] = false }
48
+ opts.on("--process", "Execute in process mode") { @options[:mode] = :process }
49
+ opts.on("--ractor", "Execute in ractor mode") do
50
+ raise ArgumentError, "Ractor mode can only be used in Ruby 3.0 and forward" if RUBY_VERSION < "3.0.0"
51
+
52
+ @options[:mode] = :ractor
53
+ end
54
+
55
+ opts.on("--editor=EDITOR", "The editor to open test files with in interactive mode") do |value|
56
+ options[:editor] = value
57
+ end
58
+ end.parse!(args)
59
+ end
60
+
61
+ # @return [void]
62
+ def start
63
+ require_tests
64
+ executor = @options[:mode] == :ractor ? RactorExecutor.new(@options) : ProcessExecutor.new(@options)
65
+ exit(executor.run)
66
+ end
67
+
68
+ # @return [void]
69
+ def require_tests
70
+ require "#{Dir.pwd}/test/test_helper"
71
+
72
+ if @files.empty?
73
+ Dir["#{Dir.pwd}/test/**/*_test.rb"].each { |f| require f }
74
+ else
75
+ @files.each do |f|
76
+ file, line_number = f.split(":")
77
+ require File.expand_path(file)
78
+ Test.add_line_number(line_number) if line_number
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Loupe
4
+ # Color
5
+ #
6
+ # This class is responsible for coloring
7
+ # strings.
8
+ class Color
9
+ # @return [Hash<Symbol, String>]
10
+ COLORS = {
11
+ red: "31",
12
+ green: "32",
13
+ yellow: "33"
14
+ }.freeze
15
+
16
+ # @param enabled [Boolean]
17
+ def initialize(enabled)
18
+ @enabled = enabled
19
+ end
20
+
21
+ # @param string [String, Symbol]
22
+ # @param color [Symbol]
23
+ # @return [String]
24
+ def p(string, color)
25
+ return string unless @enabled
26
+
27
+ color_code = COLORS[color]
28
+ "\033[1;#{color_code}m#{string}\033[0m"
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "etc"
4
+
5
+ module Loupe
6
+ # Executor
7
+ #
8
+ # This abstract parent class is responsible for providing the basics
9
+ # for executors. Concrete classes are the process and ractor executors.
10
+ class Executor
11
+ # @param options [Hash<Symbol, BasicObject>]
12
+ # @return [Loupe::Executor]
13
+ def initialize(options)
14
+ @options = options
15
+ @queue = populate_queue
16
+ @reporter = options[:interactive] ? PagedReporter.new(options) : PlainReporter.new(options)
17
+ end
18
+
19
+ # @return [Integer]
20
+ def run
21
+ raise NotImplementedError, "Concrete implementations of executors should implement the run method"
22
+ end
23
+
24
+ private
25
+
26
+ # Populate the test queue with entries including
27
+ # the class and the test method to be executed.
28
+ # E.g.:
29
+ # [[MyTest, :test_something], [AnotherTest, :test_another_thing]]
30
+ #
31
+ # @return [Array<Array<Class, Symbol>>]
32
+ def populate_queue
33
+ Test.classes.flat_map do |klass, line_numbers|
34
+ list = klass.test_list
35
+
36
+ unless line_numbers.empty?
37
+ list.select! do |method_name|
38
+ line_numbers.include?(klass.instance_method(method_name).source_location.last.to_s)
39
+ end
40
+ end
41
+
42
+ list.map! { |method| [klass, method] }.shuffle!
43
+ end
44
+ end
45
+ end
46
+ end