minitag 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 683ead8b294111fdcd7f19206a0713fb85bc8ed632aa3a853fcbe72a75abf1d2
4
- data.tar.gz: 3100c71052cd5fc151f0107d76e1abddcc6fe36d1ec649d3d2fc3c385cc5bfa5
3
+ metadata.gz: 9aa792615d3b3d3022a0b1372c951f0f7394a184bd296f9ca760fb69d2be7b2d
4
+ data.tar.gz: ca7add04befa59ab7141cfccc41475def049d1e3e7596bfcbaeecef55e909144
5
5
  SHA512:
6
- metadata.gz: a46ab8f1155605202144a8d6c44a4250409cd0ae94670fe1f748b7c08aacac9c937b9ea97488c7131d00f559be2ea52fc99a5cbb4f430f243e7ddaed0cc01b2d
7
- data.tar.gz: ffa35e8148558a1210e48d92fc3672bae8d46e6eba4f9da49fde5142661af5b90f82d99db56da4d6aa07841b89056ef7012b66da6bbb105ddae64a7a5ea888ae
6
+ metadata.gz: 62a382576471b5223651ab76083afda212b43820dad6e23cc59d095d153e85048d81d09f38f3ca4d2f57dd758316f49ccedc48afd8ad99f84aa2554886d3ec24
7
+ data.tar.gz: 2fee9e38e0cfde38bdb4a71c806cee5c2395c5794aa4f3e46cfc863a8b634ee299163046693d671fc759e733b97d2fdd6b30d31ab370d77c36b549842c1a1e60
@@ -0,0 +1,32 @@
1
+ name: continuous-integration
2
+ on: push
3
+
4
+ jobs:
5
+ ci:
6
+ runs-on: ubuntu-latest
7
+ steps:
8
+ - uses: actions/checkout@v2
9
+ - name: Set up Ruby 2.7
10
+ uses: actions/setup-ruby@v1
11
+ with:
12
+ ruby-version: "2.7"
13
+ - name: Cache gems
14
+ uses: actions/cache@v2
15
+ with:
16
+ path: vendor/bundle
17
+ key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
18
+ restore-keys: |
19
+ ${{ runner.os }}-gem-
20
+ - name: Install gems
21
+ run: |
22
+ sed -i '/ruby ".*"/,+1 d' Gemfile
23
+ bundle_version=`awk '/BUNDLED WITH/{getline; gsub(/ /, "", $0); print}' Gemfile.lock`
24
+ gem install bundler --no-document -v ${bundle_version}
25
+ bundle config retry "3"
26
+ bundle config jobs "$(nproc)"
27
+ bundle config path vendor/bundle
28
+ bundle install
29
+ - name: Run RuboCop
30
+ run: bundle exec rubocop --parallel
31
+ - name: Run tests
32
+ run: bundle exec rake test
data/Gemfile CHANGED
@@ -4,5 +4,7 @@ source 'https://rubygems.org'
4
4
 
5
5
  git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
6
 
7
+ ruby '>= 2.6'
8
+
7
9
  # Specify your gem's dependencies in minitag.gemspec
8
10
  gemspec
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- minitag (0.2.0)
4
+ minitag (0.4.0)
5
5
  minitest (~> 5.0)
6
6
 
7
7
  GEM
@@ -39,5 +39,8 @@ DEPENDENCIES
39
39
  rake (>= 12.3.3)
40
40
  rubocop (~> 0.87.1)
41
41
 
42
+ RUBY VERSION
43
+ ruby 2.6.3p62
44
+
42
45
  BUNDLED WITH
43
46
  1.17.2
data/README.md CHANGED
@@ -1,9 +1,7 @@
1
- [![Gem Version](https://badge.fury.io/rb/minitag.svg)](https://badge.fury.io/rb/minitag)
1
+ [![Gem Version](https://badge.fury.io/rb/minitag.svg)](https://badge.fury.io/rb/minitag) [![](https://github.com/bernardoamc/minitag/workflows/continuous-integration/badge.svg)](https://github.com/bernardoamc/minitag/actions?query=workflow%3Acontinuous-integration)
2
2
 
3
3
  # Minitag
4
4
 
5
- [![Build Status](https://travis-ci.org/bernardoamc/minitag.svg?branch=master)](https://travis-ci.org/bernardoamc/minitag)
6
-
7
5
  A simple gem that allow developers to tag their minitest tests and run tests
8
6
  based on these tags.
9
7
 
@@ -33,25 +31,30 @@ Require `minitag` in our `test_helper.rb`:
33
31
 
34
32
  We can tag specific tests with one or more tags.
35
33
 
34
+ It is important to point out that tags associated with a test have no concept of being inclusive or exclusive. This distinction is only valid for [tag filters](#running-tests-with-tag-filters).
36
35
 
37
36
  ```rb
38
37
  class MyTest < Minitest::Test
39
38
  tag 'my_tag', 'another_tag'
40
- test '#hello minitag' do
39
+ def test_hello_minitest
41
40
  # ...
42
41
  end
43
42
  end
44
43
  ```
45
44
 
46
- ### Running tests
45
+ ### Running tests with tag filters
47
46
 
48
47
  We can now run our test suite with specific tags:
49
48
 
50
- `$ bundle exec rake test --tag 'my_tag'`
49
+ `$ bundle exec rake test --tag 'unit'`
50
+
51
+ or even multiple tags:
52
+
53
+ `$ bundle exec rake test --tag 'unit' --tag 'services' --tag '~model'`
51
54
 
52
- ### Extra
55
+ ### More on tag filters
53
56
 
54
- Tags can be:
57
+ Tag filters can be:
55
58
  1. `inclusive`
56
59
  2. `exclusive` with the `~` prefix:
57
60
 
@@ -1,27 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'set'
3
4
  require 'minitest'
4
5
  require 'minitag/version'
5
- require 'minitag/tag'
6
- require 'minitag/tag_mapper'
7
- require 'minitag/tag_matcher'
6
+ require 'minitag/context'
7
+ require 'minitag/minitest_tag'
8
8
  require 'minitag/tag_extension'
9
9
 
10
10
  # Namespace for classes or modules providing tagging functionality
11
11
  # to Minitest::Test
12
12
  module Minitag
13
13
  class << self
14
- # Tags specified by the `--tag` or `-t` option.
15
- def execution_tags
16
- @execution_tags ||= []
14
+ # Execution context of the test suite.
15
+ def context
16
+ @context ||= Context.new
17
17
  end
18
18
 
19
- # Add tag specified by the `--tag` or `-t` option.
20
- def add_execution_tag(tag)
21
- execution_tags << Tag.new(tag)
19
+ # Add filtering tag to context specified by the `--tag` or `-t` option.
20
+ def add_filter(tag)
21
+ context.add_filter(tag)
22
22
  end
23
23
 
24
- # Tags from the last `tag` method waiting to be associated with a test.
24
+ # Tags from the last `tag` method awaiting to be associated with a test.
25
25
  def pending_tags
26
26
  @pending_tags || []
27
27
  end
@@ -30,10 +30,5 @@ module Minitag
30
30
  def pending_tags=(tags)
31
31
  @pending_tags = Array(tags)
32
32
  end
33
-
34
- # The mapping of tags and tests.
35
- def tag_mapping
36
- @tag_mapping ||= Minitag::TagMapper.new
37
- end
38
33
  end
39
34
  end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './tag_registry'
4
+
5
+ module Minitag
6
+ # Represents the execution context of the test suite.
7
+ class Context
8
+ def initialize
9
+ @inclusive_filters = Set.new
10
+ @exclusive_filters = Set.new
11
+ @tag_registry = Minitag::TagRegistry.new
12
+ end
13
+
14
+ # Associates tags with a name taking into account its namespace.
15
+ #
16
+ # @param [String] namespace the namespace which a name belongs.
17
+ # @param [String] name the test name.
18
+ # @param [Array] tags the collection of tags.
19
+ #
20
+ # @return [void]
21
+ def add_tags(namespace:, name:, tags:)
22
+ @tag_registry.add(namespace: namespace, name: name, tags: tags)
23
+ end
24
+
25
+ # Adds a filter tag.
26
+ # Tags with a ~ prefix are treated as exclusive filters or inclusive filters otherwise.
27
+ #
28
+ # param [String] name the name of the filter tag.
29
+ #
30
+ # Invariants:
31
+ # - A filter will always be a String without the ~ prefix.
32
+ #
33
+ # @return [void]
34
+ def add_filter(tag)
35
+ if tag.start_with?('~')
36
+ @exclusive_filters << tag[1..]
37
+ else
38
+ @inclusive_filters << tag
39
+ end
40
+ end
41
+
42
+ # Indicates when a context has no filters.
43
+ #
44
+ # @return [Boolean] whether a context has no filters.
45
+ def no_filters?
46
+ @inclusive_filters.empty? && @exclusive_filters.empty?
47
+ end
48
+
49
+ # Detects whether the name associated with a namespace contains tags
50
+ # that matches the filtering criteria. For more information check the methods:
51
+ # - match_inclusive_filters?
52
+ # - match_exclusive_filters?
53
+ #
54
+ # @param [String] namespace the namespace which a test name belongs.
55
+ # @param [String] name the test name.
56
+ #
57
+ # Invariants:
58
+ # - Returns true when no filters are present.
59
+ #
60
+ # @return [Boolean] whether there was a match or not.
61
+ def match?(namespace:, name:)
62
+ return true if no_filters?
63
+
64
+ tags = @tag_registry.fetch(namespace: namespace, name: name)
65
+ match_inclusive_filters?(tags) && match_exclusive_filters?(tags)
66
+ end
67
+
68
+ private
69
+
70
+ # Detects whether any of the tags matches the inclusive filters.
71
+ #
72
+ # @param [Set] tags the set of tags.
73
+ #
74
+ # Invariants:
75
+ # - Returns true when no inclusive filters are specified.
76
+ # - Returns false when inclusive filters are specified but there
77
+ # are no tags.
78
+ #
79
+ # @return [Boolean] whether there was a match or not.
80
+ def match_inclusive_filters?(tags)
81
+ return true if @inclusive_filters.empty?
82
+ return false if @inclusive_filters.any? && tags.empty?
83
+
84
+ (@inclusive_filters & tags).any?
85
+ end
86
+
87
+ # Detects whether any of the tags matches the exclusive filters.
88
+ #
89
+ # @param [Set] tags the set of tags.
90
+ #
91
+ # Invariants:
92
+ # - Returns true when no exclusive filters are specified.
93
+ # - Returns true when exclusive filters are specified and there
94
+ # are no tags.
95
+ #
96
+ # return [Boolean] whether there was a match or not.
97
+ def match_exclusive_filters?(tags)
98
+ return true if @exclusive_filters.empty?
99
+ return true if @exclusive_filters.any? && tags.empty?
100
+
101
+ (@exclusive_filters & tags).none?
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Minitag
4
+ # Module used to extend Minitest::Test with the tag method.
5
+ module MinitestTag
6
+ # Add tags to be associated with the next test definition and extends the
7
+ # class from which the tag method is being used with Minitag::TagExtension.
8
+ #
9
+ # It is important to notice that tags associated with a test have no concept
10
+ # of being inclusive or exclusive. This distinction is only valid for tag
11
+ # filters.
12
+ #
13
+ # @param [Array] tags the list of tags to be associated with a test case.
14
+ #
15
+ # @return [void]
16
+ def tag(*tags)
17
+ Minitag.pending_tags = tags.map { |tag| tag.to_s.strip.downcase }
18
+ singleton_class.prepend(Minitag::TagExtension)
19
+ end
20
+ end
21
+ end
22
+
23
+ Minitest::Test.singleton_class.prepend(Minitag::MinitestTag)
@@ -1,21 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Minitag
4
- # Module used to extend Minitest::Test.
4
+ # Module used to extend classes that rely on tags.
5
5
  # It has the following responsibilities:
6
- # - Introduce the tag functionality
7
6
  # - Associate tags with tests
8
7
  # - Filter tests based on the specified tags
9
8
  module TagExtension
10
- def tag(*tags)
11
- Minitag.pending_tags = tags
12
- end
13
-
14
9
  define_method(:method_added) do |name|
15
10
  if name[/\Atest_/]
16
- Minitag.pending_tags.each do |pending_tag|
17
- Minitag.tag_mapping.add(context: self, name: name, tag: pending_tag)
18
- end
11
+ Minitag.context.add_tags(
12
+ namespace: self, name: name, tags: Minitag.pending_tags
13
+ )
19
14
 
20
15
  Minitag.pending_tags = []
21
16
  end
@@ -23,20 +18,11 @@ module Minitag
23
18
 
24
19
  def runnable_methods
25
20
  methods = super.dup
26
- return methods if Minitag.execution_tags.empty? || methods.empty?
27
-
28
- execution_tags = Minitag.execution_tags
29
- inclusive_tags = execution_tags.select(&:inclusive?).map(&:name)
30
- exclusive_tags = execution_tags.select(&:exclusive?).map(&:name)
21
+ return methods if Minitag.context.no_filters?
31
22
 
32
23
  methods.select do |runnable_method|
33
- runnable_method_tags = Minitag.tag_mapping.fetch(context: self, name: runnable_method).map(&:name)
34
-
35
- Minitag::TagMatcher.inclusive_match?(inclusive_tags, runnable_method_tags) &&
36
- Minitag::TagMatcher.exclusive_match?(exclusive_tags, runnable_method_tags)
24
+ Minitag.context.match?(namespace: self, name: runnable_method)
37
25
  end
38
26
  end
39
27
  end
40
28
  end
41
-
42
- Minitest::Test.singleton_class.prepend(Minitag::TagExtension)
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Minitag
4
+ # Stores tags associated with a test name, which belongs to a namespace.
5
+ # The namespace is usually the class which the test belongs to.
6
+ class TagRegistry
7
+ def initialize
8
+ @repository = Hash.new { |h, k| h[k] = Set.new }
9
+ end
10
+
11
+ # Associates tags with a name taking into account its namespace.
12
+ #
13
+ # Duplicated tags will be removed during this operation.
14
+ #
15
+ # @param [String] namespace the context which a test name belongs.
16
+ # @param [String] name the test name.
17
+ # @param [Array] tags the collection of tags associated with a test.
18
+ #
19
+ # @return [void]
20
+ def add(namespace:, name:, tags:)
21
+ @repository[key(namespace, name)] = Set.new(tags)
22
+ end
23
+
24
+ # Fetches tags associated with a test name and namespace.
25
+ #
26
+ # @param [String] namespace the context which a test name belongs.
27
+ # @param [String] name the test name.
28
+ #
29
+ # @return [Set] the tags associated with the specified namespace and test name.
30
+ def fetch(namespace:, name:)
31
+ @repository[key(namespace, name)]
32
+ end
33
+
34
+ private
35
+
36
+ def key(namespace, name)
37
+ "#{namespace}_#{name}"
38
+ end
39
+ end
40
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Minitag
4
- VERSION = '0.2.0'
4
+ VERSION = '0.4.0'
5
5
  end
@@ -8,13 +8,13 @@ module Minitest
8
8
  def self.plugin_minitag_options(opts, options)
9
9
  opts.on '-t', '--tag TAG' do |tag|
10
10
  options[:tags] ||= []
11
- options[:tags] << tag
11
+ options[:tags] << tag.to_s.strip.downcase
12
12
  end
13
13
  end
14
14
 
15
15
  def self.plugin_minitag_init(options)
16
16
  Array(options[:tags]).each do |tag|
17
- Minitag.add_execution_tag(tag.to_s.strip)
17
+ Minitag.context.add_filter(tag)
18
18
  end
19
19
  end
20
20
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: minitag
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bernardo de Araujo
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-07-11 00:00:00.000000000 Z
11
+ date: 2020-07-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -73,9 +73,9 @@ executables: []
73
73
  extensions: []
74
74
  extra_rdoc_files: []
75
75
  files:
76
+ - ".github/workflows/ci.yml"
76
77
  - ".gitignore"
77
78
  - ".rubocop.yml"
78
- - ".travis.yml"
79
79
  - CODE_OF_CONDUCT.md
80
80
  - Gemfile
81
81
  - Gemfile.lock
@@ -85,10 +85,10 @@ files:
85
85
  - bin/console
86
86
  - bin/setup
87
87
  - lib/minitag.rb
88
- - lib/minitag/tag.rb
88
+ - lib/minitag/context.rb
89
+ - lib/minitag/minitest_tag.rb
89
90
  - lib/minitag/tag_extension.rb
90
- - lib/minitag/tag_mapper.rb
91
- - lib/minitag/tag_matcher.rb
91
+ - lib/minitag/tag_registry.rb
92
92
  - lib/minitag/version.rb
93
93
  - lib/minitest/minitag_plugin.rb
94
94
  - minitag.gemspec
@@ -1,7 +0,0 @@
1
- ---
2
- sudo: false
3
- language: ruby
4
- cache: bundler
5
- rvm:
6
- - 2.6.3
7
- before_install: gem install bundler -v 1.17.2
@@ -1,44 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Minitag
4
- # Represents a tag in our system.
5
- # Provides helper methods to identify the type of a tag, which can
6
- # only one of the following:
7
- # - inclusive
8
- # - exclusive (name with ~ as a prefix)
9
- class Tag
10
- attr_reader :name
11
-
12
- # Initializes a tag. Tags with a ~ prefix are deemed exclusive and
13
- # inclusive otherwise.
14
- #
15
- # param [String] name the name of the tag
16
- #
17
- # Invariants:
18
- # - A tag name will always be a String without the ~ prefix
19
- # after initialization.
20
- def initialize(name)
21
- @name = name.to_s
22
- @exclusive = false
23
-
24
- return unless @name.start_with?('~')
25
-
26
- @name = @name[1..-1]
27
- @exclusive = true
28
- end
29
-
30
- # Whether this tag needs to be excluded or not.
31
- #
32
- # return [boolean]
33
- def exclusive?
34
- @exclusive
35
- end
36
-
37
- # Whether this tag needs to be included or not.
38
- #
39
- # return [boolean]
40
- def inclusive?
41
- !exclusive?
42
- end
43
- end
44
- end
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Minitag
4
- # Stores the mapping between a test name and its existing tags.
5
- class TagMapper
6
- def initialize
7
- @repository = Hash.new { |h, k| h[k] = [] }
8
- end
9
-
10
- # Associates a tag with a test name takinto into account is context.
11
- #
12
- # @param [String] context the context which a test name belongs.
13
- # @param [String] name the test name.
14
- # @param [String] tag the tag name.
15
- #
16
- # @return [void]
17
- def add(context:, name:, tag:)
18
- @repository[key(context, name)] << Minitag::Tag.new(tag)
19
- end
20
-
21
- # Fetches tags associated with a test name and context.
22
- #
23
- # @param [String] context the context which a test name belongs.
24
- # @param [String] name the test name.
25
- #
26
- # @return [Array] the tags associated with the test.
27
- def fetch(context:, name:)
28
- @repository[key(context, name)]
29
- end
30
-
31
- private
32
-
33
- def key(context, name)
34
- "#{context}_#{name}"
35
- end
36
- end
37
- end
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Minitag
4
- # Introduce different match? methods based on the type of the chosen tags.
5
- class TagMatcher
6
- # Detects if any of the desired tags match any of the existing ones.
7
- #
8
- # @param [Array] desired_tags the tags specified by `--tag`.
9
- # @param [Array] existing_tags the tags specified by the `tag` method.
10
- #
11
- # Invariants:
12
- # - Always returns true when no desired tags are specified.
13
- # - Always returns false when desired tags are specified but there are
14
- # no existing tags.
15
- #
16
- # return [boolean] whether there was a match or not.
17
- def self.inclusive_match?(desired_tags, existing_tags)
18
- return true if desired_tags.empty?
19
- return false if desired_tags.any? && existing_tags.empty?
20
-
21
- (desired_tags & existing_tags).any?
22
- end
23
-
24
- # Detects if there are no matches between any of the desired tags
25
- # and existing ones.
26
- #
27
- # @param desired_tags [Array] tags specified by `--tag`.
28
- # @param existing_tags [Array] tags specified by the `tag` method.
29
- #
30
- # Invariants:
31
- # - Always returns true when no desired tags are specified.
32
- # - Always returns true when desired tags are specified but there are
33
- # no existing tags.
34
- #
35
- # return [boolean] whether there was no match or not.
36
- def self.exclusive_match?(desired_tags, existing_tags)
37
- return true if desired_tags.empty?
38
- return true if desired_tags.any? && existing_tags.empty?
39
-
40
- (desired_tags & existing_tags).none?
41
- end
42
- end
43
- end