minitag 0.1.0 → 0.3.3

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: 76f72fc7c148e87021a776dea6655c3a676be7878e2fd595f436244d3bf7a998
4
- data.tar.gz: aeea971c7ccab5fe5761e80c9d867b191b954e28693f7ceeedcd6f7d2154036a
3
+ metadata.gz: ab98e9f2debb23b27f21435a3b2ac0102a835391accb97fd7e1bae69457f4e07
4
+ data.tar.gz: 538c9bc1789dd2080b3a8f8430c116d05693eb5c2dfee3f1204fe60c67e5e80a
5
5
  SHA512:
6
- metadata.gz: e8d68e32988f3a13a2252b0578cf21efd6630befe8563a21869aa00c58abb63deb7d46d598fb9e6d6740b84b9be9e13214aafd8e1c2f05995ffdca766a380bc2
7
- data.tar.gz: 527b5dcad48e756611bc662a587197ebd3bb257532b34b056c13f47deb2f6d9d4ec19b782c7812adc80cdd088f7e437f27d6066af6d4063ce3ed90fd5b6b00a6
6
+ metadata.gz: '081ad2c7553abb897271b01e30d0561255ca5ee30128aac47f4dbee4ac29fd7a0de0e1954268c9325f9476823f6ffc16321b429fc9d98b0edc3fc20e10433ca6'
7
+ data.tar.gz: 372cd5e85be6c5f7c35faf779e2ab197ac8a4d218dd11b13c43c2b5be2449aa7c14d4898208cc93c5410a24b2ec4e6290171c1d67dca332d8f9325706cb2eace
@@ -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.1.0)
4
+ minitag (0.3.3)
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,6 +1,6 @@
1
- # Minitag
1
+ [![Gem Version](https://badge.fury.io/rb/minitag.svg)](https://badge.fury.io/rb/minitag)
2
2
 
3
- [![Build Status](https://travis-ci.org/bernardoamc/minitag.svg?branch=master)](https://travis-ci.org/bernardoamc/minitag)
3
+ # Minitag
4
4
 
5
5
  A simple gem that allow developers to tag their minitest tests and run tests
6
6
  based on these tags.
@@ -31,25 +31,30 @@ Require `minitag` in our `test_helper.rb`:
31
31
 
32
32
  We can tag specific tests with one or more tags.
33
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).
34
35
 
35
36
  ```rb
36
37
  class MyTest < Minitest::Test
37
38
  tag 'my_tag', 'another_tag'
38
- test '#hello minitag' do
39
+ def test_hello_minitest
39
40
  # ...
40
41
  end
41
42
  end
42
43
  ```
43
44
 
44
- ### Running tests
45
+ ### Running tests with tag filters
45
46
 
46
47
  We can now run our test suite with specific tags:
47
48
 
48
- `$ 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'`
49
54
 
50
- ### Extra
55
+ ### More on tag filters
51
56
 
52
- Tags can be:
57
+ Tag filters can be:
53
58
  1. `inclusive`
54
59
  2. `exclusive` with the `~` prefix:
55
60
 
@@ -2,33 +2,31 @@
2
2
 
3
3
  require 'minitest'
4
4
  require 'minitag/version'
5
- require 'minitag/tag'
6
- require 'minitag/tag_mapper'
7
- require 'minitag/tag_matcher'
5
+ require 'minitag/context'
8
6
  require 'minitag/tag_extension'
9
7
 
10
8
  # Namespace for classes or modules providing tagging functionality
11
9
  # to Minitest::Test
12
10
  module Minitag
13
11
  class << self
14
- def execution_tags
15
- @execution_tags ||= []
12
+ # Execution context of the test suite.
13
+ def context
14
+ @context ||= Context.new
16
15
  end
17
16
 
18
- def add_execution_tag(tag)
19
- execution_tags << Tag.new(tag)
17
+ # Add filtering tag to context specified by the `--tag` or `-t` option.
18
+ def add_filter(tag)
19
+ context.add_filter(tag)
20
20
  end
21
21
 
22
+ # Tags from the last `tag` method awaiting to be associated with a test.
22
23
  def pending_tags
23
24
  @pending_tags || []
24
25
  end
25
26
 
27
+ # Tags set from the `tag` method.
26
28
  def pending_tags=(tags)
27
29
  @pending_tags = Array(tags)
28
30
  end
29
-
30
- def tag_mapping
31
- @tag_mapping ||= Minitag::TagMapper.new
32
- end
33
31
  end
34
32
  end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+ require_relative './tag_registry'
5
+
6
+ module Minitag
7
+ # Represents the execution context of the test suite.
8
+ class Context
9
+ def initialize
10
+ @inclusive_filters = Set.new
11
+ @exclusive_filters = Set.new
12
+ @tag_registry = Minitag::TagRegistry.new
13
+ end
14
+
15
+ # Associates tags with a name taking into account its namespace.
16
+ #
17
+ # @param [String] namespace the namespace which a name belongs.
18
+ # @param [String] name the test name.
19
+ # @param [Array] tags the collection of tags.
20
+ #
21
+ # @return [void]
22
+ def add_tags(namespace:, name:, tags:)
23
+ @tag_registry.add(namespace: namespace, name: name, tags: tags)
24
+ end
25
+
26
+ # Adds a filter tag.
27
+ # Tags with a ~ prefix are treated as exclusive filters or inclusive filters otherwise.
28
+ #
29
+ # param [String] name the name of the filter tag.
30
+ #
31
+ # Invariants:
32
+ # - A filter will always be a String without the ~ prefix.
33
+ #
34
+ # @return [void]
35
+ def add_filter(tag)
36
+ if tag.start_with?('~')
37
+ @exclusive_filters << tag[1..]
38
+ else
39
+ @inclusive_filters << tag
40
+ end
41
+ end
42
+
43
+ # Indicates when a context has no filters.
44
+ #
45
+ # @return [Boolean] whether a context has no filters.
46
+ def no_filters?
47
+ @inclusive_filters.empty? && @exclusive_filters.empty?
48
+ end
49
+
50
+ # Detects whether the name associated with a namespace contains tags
51
+ # that matches the filtering criteria. For more information check the methods:
52
+ # - match_inclusive_filters?
53
+ # - match_exclusive_filters?
54
+ #
55
+ # @param [String] namespace the namespace which a test name belongs.
56
+ # @param [String] name the test name.
57
+ #
58
+ # Invariants:
59
+ # - Returns true when no filters are present.
60
+ #
61
+ # @return [Boolean] whether there was a match or not.
62
+ def match?(namespace:, name:)
63
+ return true if no_filters?
64
+
65
+ tags = @tag_registry.fetch(namespace: namespace, name: name)
66
+ match_inclusive_filters?(tags) && match_exclusive_filters?(tags)
67
+ end
68
+
69
+ private
70
+
71
+ # Detects whether any of the tags matches the inclusive filters.
72
+ #
73
+ # @param [Set] tags the set of tags.
74
+ #
75
+ # Invariants:
76
+ # - Returns true when no inclusive filters are specified.
77
+ # - Returns false when inclusive filters are specified but there
78
+ # are no tags.
79
+ #
80
+ # @return [Boolean] whether there was a match or not.
81
+ def match_inclusive_filters?(tags)
82
+ return true if @inclusive_filters.empty?
83
+ return false if @inclusive_filters.any? && tags.empty?
84
+
85
+ (@inclusive_filters & tags).any?
86
+ end
87
+
88
+ # Detects whether any of the tags matches the exclusive filters.
89
+ #
90
+ # @param [Set] tags the set of tags.
91
+ #
92
+ # Invariants:
93
+ # - Returns true when no exclusive filters are specified.
94
+ # - Returns true when exclusive filters are specified and there
95
+ # are no tags.
96
+ #
97
+ # return [Boolean] whether there was a match or not.
98
+ def match_exclusive_filters?(tags)
99
+ return true if @exclusive_filters.empty?
100
+ return true if @exclusive_filters.any? && tags.empty?
101
+
102
+ (@exclusive_filters & tags).none?
103
+ end
104
+ end
105
+ end
@@ -7,15 +7,24 @@ module Minitag
7
7
  # - Associate tags with tests
8
8
  # - Filter tests based on the specified tags
9
9
  module TagExtension
10
+ # Add tags to be associated with the next test definition.
11
+ #
12
+ # It is important to notice that tags associated with a test have no concept
13
+ # of being inclusive or exclusive. This distinction is only valid for tag
14
+ # filters.
15
+ #
16
+ # @param [Array] tags the list of tags to be associated with a test case.
17
+ #
18
+ # @return [void]
10
19
  def tag(*tags)
11
- Minitag.pending_tags = tags
20
+ Minitag.pending_tags = tags.map { |tag| tag.to_s.strip.downcase }
12
21
  end
13
22
 
14
23
  define_method(:method_added) do |name|
15
24
  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
25
+ Minitag.context.add_tags(
26
+ namespace: self, name: name, tags: Minitag.pending_tags
27
+ )
19
28
 
20
29
  Minitag.pending_tags = []
21
30
  end
@@ -23,17 +32,10 @@ module Minitag
23
32
 
24
33
  def runnable_methods
25
34
  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)
35
+ return methods if Minitag.context.no_filters?
31
36
 
32
37
  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)
38
+ Minitag.context.match?(namespace: self, name: runnable_method)
37
39
  end
38
40
  end
39
41
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ module Minitag
6
+ # Stores tags associated with a test name, which belongs to a namespace.
7
+ # The namespace is usually the class which the test belongs to.
8
+ class TagRegistry
9
+ def initialize
10
+ @repository = Hash.new { |h, k| h[k] = Set.new }
11
+ end
12
+
13
+ # Associates tags with a name taking into account its namespace.
14
+ #
15
+ # Duplicated tags will be removed during this operation.
16
+ #
17
+ # @param [String] namespace the context which a test name belongs.
18
+ # @param [String] name the test name.
19
+ # @param [Array] tags the collection of tags associated with a test.
20
+ #
21
+ # @return [void]
22
+ def add(namespace:, name:, tags:)
23
+ @repository[key(namespace, name)] = Set.new(tags)
24
+ end
25
+
26
+ # Fetches tags associated with a test name and namespace.
27
+ #
28
+ # @param [String] namespace the context which a test name belongs.
29
+ # @param [String] name the test name.
30
+ #
31
+ # @return [Set] the tags associated with the specified namespace and test name.
32
+ def fetch(namespace:, name:)
33
+ @repository[key(namespace, name)]
34
+ end
35
+
36
+ private
37
+
38
+ def key(namespace, name)
39
+ "#{namespace}_#{name}"
40
+ end
41
+ end
42
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Minitag
4
- VERSION = '0.1.0'
4
+ VERSION = '0.3.3'
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.1.0
4
+ version: 0.3.3
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-12 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,9 @@ 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
89
  - lib/minitag/tag_extension.rb
90
- - lib/minitag/tag_mapper.rb
91
- - lib/minitag/tag_matcher.rb
90
+ - lib/minitag/tag_registry.rb
92
91
  - lib/minitag/version.rb
93
92
  - lib/minitest/minitag_plugin.rb
94
93
  - 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,30 +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
- def initialize(name)
13
- @name = name.to_s
14
- @exclusive = false
15
-
16
- return unless @name.start_with?('~')
17
-
18
- @name = @name[1..-1]
19
- @exclusive = true
20
- end
21
-
22
- def exclusive?
23
- @exclusive
24
- end
25
-
26
- def inclusive?
27
- !exclusive?
28
- end
29
- end
30
- end
@@ -1,26 +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
- attr_reader :repository
7
-
8
- def initialize
9
- @repository = Hash.new { |h, k| h[k] = [] }
10
- end
11
-
12
- def add(context:, name:, tag:)
13
- @repository[key(context, name)] << Minitag::Tag.new(tag)
14
- end
15
-
16
- def fetch(context:, name:)
17
- @repository[key(context, name)]
18
- end
19
-
20
- private
21
-
22
- def key(context, name)
23
- "#{context}_#{name}"
24
- end
25
- end
26
- 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