From 4d5710acb7411960580d90b3a9d9ff7999e268dd Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Mon, 8 Apr 2024 04:04:16 +0000 Subject: [PATCH] Remove mutant-license gem mechanism --- Changelog.md | 10 + Gemfile.lock | 8 +- Gemfile.shared | 6 - README.md | 48 +- lib/mutant.rb | 6 - lib/mutant/cli/command.rb | 8 - lib/mutant/cli/command/environment/run.rb | 3 +- lib/mutant/cli/command/root.rb | 2 +- lib/mutant/cli/command/subscription.rb | 54 -- lib/mutant/license.rb | 46 -- lib/mutant/license/subscription.rb | 69 --- lib/mutant/license/subscription/commercial.rb | 88 --- lib/mutant/license/subscription/opensource.rb | 30 - lib/mutant/license/subscription/repository.rb | 72 --- lib/mutant/version.rb | 2 +- spec/unit/mutant/cli_spec.rb | 548 ++++++++---------- spec/unit/mutant/license/repository_spec.rb | 280 --------- spec/unit/mutant/license/subscription_spec.rb | 359 ------------ spec/unit/mutant/license_spec.rb | 125 ---- test_app/Gemfile.rspec3.10.lock | 12 +- test_app/Gemfile.rspec3.11.lock | 12 +- test_app/Gemfile.rspec3.12.lock | 12 +- test_app/Gemfile.rspec3.13.lock | 12 +- test_app/Gemfile.rspec3.8.lock | 12 +- test_app/Gemfile.rspec3.9.lock | 12 +- 25 files changed, 263 insertions(+), 1573 deletions(-) delete mode 100644 lib/mutant/cli/command/subscription.rb delete mode 100644 lib/mutant/license.rb delete mode 100644 lib/mutant/license/subscription.rb delete mode 100644 lib/mutant/license/subscription/commercial.rb delete mode 100644 lib/mutant/license/subscription/opensource.rb delete mode 100644 lib/mutant/license/subscription/repository.rb delete mode 100644 spec/unit/mutant/license/repository_spec.rb delete mode 100644 spec/unit/mutant/license/subscription_spec.rb delete mode 100644 spec/unit/mutant/license_spec.rb diff --git a/Changelog.md b/Changelog.md index dfea086de..7714ecb61 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,13 @@ +# v0.12.0 [unreleased] + + Drop the license gem, entirely. This removes the last bit of DRM from mutants code base. + Mutant is *still* comercial software that requires payment if used on a commercial code base! + + Migration: + + * Commercial users: Add `usage: commercial` to your config file (or `--usage commercail` to your CLI) + * Opensource users: Add `usage: opensource` to your config file (or `--usage commercail` to your CLI) + # v0.11.34 2024-03-26 * [#1432](https://github.com/mbj/mutant/pull/1432) diff --git a/Gemfile.lock b/Gemfile.lock index 38c0b55a9..be48e787a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,18 +1,13 @@ PATH remote: . specs: - mutant (0.11.34) + mutant (0.12.0) diff-lcs (~> 1.3) parser (~> 3.3.0) regexp_parser (~> 2.9.0) sorbet-runtime (~> 0.5.0) unparser (~> 0.6.9) -GEM - remote: https://oss:Px2ENN7S91OmWaD5G7MIQJi1dmtmYrEh@gem.mutant.dev/ - specs: - mutant-license (0.1.1.2.2355046999240944981729280251890364410689.5) - GEM remote: https://rubygems.org/ specs: @@ -69,7 +64,6 @@ PLATFORMS DEPENDENCIES mutant! - mutant-license! rspec (~> 3.10) rspec-core (~> 3.10) rspec-its (~> 1.3.0) diff --git a/Gemfile.shared b/Gemfile.shared index 59120bb3b..e69de29bb 100644 --- a/Gemfile.shared +++ b/Gemfile.shared @@ -1,6 +0,0 @@ -# Mutant itself uses an opensource license key. -# Scoped to https://github.com/mbj/mutant it'll -# not be useful elsewhere. -source 'https://oss:Px2ENN7S91OmWaD5G7MIQJi1dmtmYrEh@gem.mutant.dev' do - gem 'mutant-license' -end diff --git a/README.md b/README.md index 7bdbb363f..6891cb461 100644 --- a/README.md +++ b/README.md @@ -77,50 +77,9 @@ Labels: ## Licensing -Mutant is commercial software, with a free usage plan for opensource projects. +Mutant is commercial software, with a free usage option for opensource projects. -Commercial projects have to acquire a license per developer, with unlimited repositories -per developer. CI usage for licensed developers is included. - -Opensource projects have to acquire their free license per repository. That license will -work for any contributor implicitly. Typically the project maintainer gets the license. - -The license distribution happens through the `mutant-license` gem. Mutant installs without -that dependency, but will not be very cooperative unless `mutant-license` is also available. - -The license gem is dynamically generated per licensee and comes with a unique license gem source -URL. - -After signup for a license the following has to be added to your `Gemfile` replacing `${key}` -with the license key and `${plan}` with `com` for commercial or `oss` for opensource usage. - -```ruby -source 'https://${plan}:${key}@gem.mutant.dev' do - gem 'mutant-license' -end -``` - -The mutant license gem contains metadata that allows mutant to verify licensed use. - -For commercial licenses mutant checks the git commit author or the configured git email -to be in the set of licensed developers. - -For opensource licenses mutant checks the git remotes against the licensed git repositories. -This allows the project maintainer to sign up and not bother collaborators with the details. - -There are, apart from initial license gem installation, no remote interaction for -license validation. - -### Getting an Opensource license - -As stated above: Opensource projects of any kind are free to use mutant. - -Just mail [me](mailto:mbj@schirp-dso.com?subject=Mutant%20Opensource%20License): Please -include: - -* Just the git remote URL of your repository. Repository can be anywhere, must not be on Github, just has to be public. - -I do not need any more details. +Commercial projects have to pay a subscription fee. ### Pricing @@ -137,7 +96,7 @@ For commercial use mutants pricing is subscription based. Costs are **per developer using mutant on any number of repositories**. -Volume licenses with custom plans are available on request. +Volume subscriptions with custom plans are available on request. Should you want to procure a commercial mutant subscription please [mail me](mailto:mbj@schirp-dso.com?subject=Mutant%20Commercial%20License). @@ -148,7 +107,6 @@ Please include the following information: * Only for the EU: A valid VAT-ID is *required*, no sales to private customers to avoid the horrors cross border VAT / MOSS. VAT for customers outside of Malta will use **reverse charging**. -* *Per developer* the git author email address as returned by `git config user.email` Also feel free to ask any other question I forgot to proactively answer here. diff --git a/lib/mutant.rb b/lib/mutant.rb index 79c9135ad..26bb845e3 100644 --- a/lib/mutant.rb +++ b/lib/mutant.rb @@ -221,7 +221,6 @@ module Mutant require 'mutant/config/coverage_criteria' require 'mutant/cli' require 'mutant/cli/command' - require 'mutant/cli/command/subscription' require 'mutant/cli/command/environment' require 'mutant/cli/command/environment/irb' require 'mutant/cli/command/environment/run' @@ -255,11 +254,6 @@ module Mutant require 'mutant/repository/diff/ranges' require 'mutant/zombifier' require 'mutant/range' - require 'mutant/license' - require 'mutant/license/subscription' - require 'mutant/license/subscription/commercial' - require 'mutant/license/subscription/opensource' - require 'mutant/license/subscription/repository' require 'mutant/segment' require 'mutant/segment/recorder' end diff --git a/lib/mutant/cli/command.rb b/lib/mutant/cli/command.rb index 6ae277158..55263faa9 100644 --- a/lib/mutant/cli/command.rb +++ b/lib/mutant/cli/command.rb @@ -176,14 +176,6 @@ def parse_remaining(remaining) end end - def parse_remaining_arguments(remaining) - if remaining.any? - Either::Left.new("#{full_name}: Does not expect extra arguments") - else - Either::Right.new(self) - end - end - def parse_subcommand(arguments) command_name, *arguments = arguments diff --git a/lib/mutant/cli/command/environment/run.rb b/lib/mutant/cli/command/environment/run.rb index 89040c945..2cd665b4d 100644 --- a/lib/mutant/cli/command/environment/run.rb +++ b/lib/mutant/cli/command/environment/run.rb @@ -34,8 +34,7 @@ class Run < self private def action - License.call(world) - .bind { bootstrap } + bootstrap .bind(&method(:validate_tests)) .bind(&Mutation::Runner.public_method(:call)) .bind(&method(:from_result)) diff --git a/lib/mutant/cli/command/root.rb b/lib/mutant/cli/command/root.rb index f3a857709..8888a3be4 100644 --- a/lib/mutant/cli/command/root.rb +++ b/lib/mutant/cli/command/root.rb @@ -10,7 +10,7 @@ class Environment < self class Root < self NAME = 'mutant' SHORT_DESCRIPTION = 'mutation testing engine main command' - SUBCOMMANDS = [Environment::Run, Environment, Subscription, Util].freeze + SUBCOMMANDS = [Environment::Run, Environment, Util].freeze end # Root end # Command end # CLI diff --git a/lib/mutant/cli/command/subscription.rb b/lib/mutant/cli/command/subscription.rb deleted file mode 100644 index 696885bc3..000000000 --- a/lib/mutant/cli/command/subscription.rb +++ /dev/null @@ -1,54 +0,0 @@ -# frozen_string_literal: true - -module Mutant - module CLI - class Command - class Subscription < self - NAME = 'subscription' - SHORT_DESCRIPTION = 'Subscription subcommands' - - private - - def license - License.call(world) - end - - class Test < self - NAME = 'test' - SUBCOMMANDS = [].freeze - SHORT_DESCRIPTION = 'Silently validates subscription, exits accordingly' - - private - - def execute - license.right? - end - end # Test - - class Show < self - NAME = 'show' - SUBCOMMANDS = [].freeze - SHORT_DESCRIPTION = 'Show subscription status' - - private - - def execute - license.either(method(:unlicensed), method(:licensed)) - end - - def licensed(subscription) - world.stdout.puts(subscription.description) - true - end - - def unlicensed(message) - world.stderr.puts(message) - false - end - end # Show - - SUBCOMMANDS = [Show, Test].freeze - end # Subscription - end # Command - end # CLI -end # Mutant diff --git a/lib/mutant/license.rb b/lib/mutant/license.rb deleted file mode 100644 index 4e346e8df..000000000 --- a/lib/mutant/license.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -module Mutant - module License - NAME = 'mutant-license' - VERSION = ['>= 0.1', '< 0.3'].freeze - - # Load license - # - # @param [World] world - # - # @return [Either] - # - # @api private - def self.call(world) - load_mutant_license(world) - .fmap { license_path(world) } - .bind { |path| Subscription.load(world, world.json.load(path)) } - end - - def self.load_mutant_license(world) - Either - .wrap_error(LoadError) { world.gem_method.call(NAME, *VERSION) } - .lmap(&:message) - .lmap(&method(:check_for_rubygems_mutant_license)) - end - private_class_method :load_mutant_license - - def self.check_for_rubygems_mutant_license(message) - if message.include?('already activated mutant-license-0.0.0') - 'mutant-license gem from rubygems.org is a dummy' - else - message - end - end - private_class_method :check_for_rubygems_mutant_license - - def self.license_path(world) - world - .pathname - .new(world.gem.loaded_specs.fetch(NAME).full_gem_path) - .join('license.json') - end - private_class_method :license_path - end -end diff --git a/lib/mutant/license/subscription.rb b/lib/mutant/license/subscription.rb deleted file mode 100644 index 36729dc9b..000000000 --- a/lib/mutant/license/subscription.rb +++ /dev/null @@ -1,69 +0,0 @@ -# frozen_string_literal: true - -module Mutant - module License - class Subscription - include Anima.new(:licensed) - - FORMAT = <<~'MESSAGE' - %s subscription: - Licensed: - %s - MESSAGE - - FAILURE_FORMAT = <<~'MESSAGE' - Can not validate %s license. - Licensed: - %s - Present: - %s - MESSAGE - - # Load value into subscription - # - # @param [Object] value - # - # @return [Subscription] - def self.load(world, value) - { - 'com' => Commercial, - 'oss' => Opensource - }.fetch(value.fetch('type')) - .from_json(value.fetch('contents')) - .call(world) - end - - # Subscription self description - # - # @return [String] - def description - FORMAT % { - licensed: licensed.to_a.join("\n"), - subscription_name: subscription_name - } - end - - private - - def failure(expected, actual) - Either::Left.new(failure_message(expected, actual)) - end - - def success - Either::Right.new(self) - end - - def subscription_name - self.class::SUBSCRIPTION_NAME - end - - def failure_message(expected, actual) - FAILURE_FORMAT % { - actual: actual.any? ? actual.map(&:to_s).join("\n") : '[none]', - expected: expected.map(&:to_s).join("\n"), - subscription_name: subscription_name - } - end - end # Subscription - end # License -end # Mutant diff --git a/lib/mutant/license/subscription/commercial.rb b/lib/mutant/license/subscription/commercial.rb deleted file mode 100644 index 3fd626960..000000000 --- a/lib/mutant/license/subscription/commercial.rb +++ /dev/null @@ -1,88 +0,0 @@ -# frozen_string_literal: true - -module Mutant - module License - class Subscription - class Commercial < self - include AbstractType - - def self.from_json(value) - { - 'individual' => Individual, - 'organization' => Organization - }.fetch(value.fetch('type', 'individual')).from_json(value) - end - - class Organization < self - SUBSCRIPTION_NAME = 'commercial organization' - - def self.from_json(value) - new(licensed: value.fetch('repositories').map(&Repository.public_method(:parse)).to_set) - end - - def call(world) - Repository.load_from_git(world).bind(&method(:check_subscription)) - end - - private - - def check_subscription(actual) - if licensed.any? { |repository| actual.any? { |other| repository.allow?(other) } } - success - else - failure(licensed, actual) - end - end - end - - class Individual < self - SUBSCRIPTION_NAME = 'commercial individual' - - class Author - include Anima.new(:email) - - alias_method :to_s, :email - public :to_s - end - - def self.from_json(value) - new(licensed: value.fetch('authors').to_set { |email| Author.new(email: email) }) - end - - def call(world) - candidates = candidates(world) - - if (licensed & candidates).any? - success - else - failure(licensed, candidates) - end - end - - private - - def candidates(world) - git_author(world).merge(commit_author(world)) - end - - def git_author(world) - capture(world, %w[git config --get user.email]) - end - - def commit_author(world) - capture(world, %w[git show --quiet --pretty=format:%ae]) - end - - def capture(world, command) - world - .capture_stdout(command) - .fmap(&:chomp) - .fmap { |email| Author.new(email: email) } - .fmap { |value| Set.new([value]) } - .from_right { Set.new } - end - end # Individual - end # Commercial - end # Subscription - end # License -end # Mutant diff --git a/lib/mutant/license/subscription/opensource.rb b/lib/mutant/license/subscription/opensource.rb deleted file mode 100644 index 0256a0072..000000000 --- a/lib/mutant/license/subscription/opensource.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -module Mutant - module License - class Subscription - class Opensource < self - SUBSCRIPTION_NAME = 'opensource repository' - - def self.from_json(value) - new(licensed: value.fetch('repositories').map(&Repository.public_method(:parse)).to_set) - end - - def call(world) - Repository.load_from_git(world).bind(&method(:check_subscription)) - end - - private - - def check_subscription(actual) - if licensed.any? { |repository| actual.any? { |other| repository.allow?(other) } } - success - else - failure(licensed, actual) - end - end - - end # Opensource - end # Subscription - end # License -end # Mutant diff --git a/lib/mutant/license/subscription/repository.rb b/lib/mutant/license/subscription/repository.rb deleted file mode 100644 index 880ef00dc..000000000 --- a/lib/mutant/license/subscription/repository.rb +++ /dev/null @@ -1,72 +0,0 @@ -# frozen_string_literal: true - -module Mutant - module License - class Subscription - class Repository - include Anima.new(:host, :path) - - REMOTE_REGEXP = /\A[^\t]+\t(?[^ ]+) \((?:fetch|push)\)\n\z/ - GIT_SSH_REGEXP = %r{\A[^@]+@(?[^:/]+)[:/](?.+?)(?:\.git)?\z} - GIT_HTTPS_REGEXP = %r{\Ahttps://(?[^/]+)/(?.+?)(?:\.git)?\z} - WILDCARD = '/*' - WILDCARD_RANGE = (..-WILDCARD.length) - - private_constant(*constants(false)) - - def to_s - [host, path].join('/') - end - - def self.load_from_git(world) - world - .capture_stdout(%w[git remote --verbose]) - .fmap(&method(:parse_remotes)) - end - - def self.parse_remotes(input) - input.lines.map(&method(:parse_remote)).to_set - end - private_class_method :parse_remotes - - def self.parse(input) - host, path = *input.split('/', 2).map(&:downcase) - new(host: host, path: path) - end - - def self.parse_remote(input) - match = REMOTE_REGEXP.match(input) or - fail "Unmatched remote line: #{input.inspect}" - - parse_url(match[:url]) - end - private_class_method :parse_remote - - def self.parse_url(input) - match = GIT_SSH_REGEXP.match(input) || GIT_HTTPS_REGEXP.match(input) - - unless match - fail "Unmatched git remote URL: #{input.inspect}" - end - - new(host: match[:host], path: match[:path].downcase) - end - private_class_method :parse_url - - def allow?(other) - other.host.eql?(host) && path_match?(other.path) - end - - private - - def path_match?(other_path) - path.eql?(other_path) || wildcard_match?(path, other_path) || wildcard_match?(other_path, path) - end - - def wildcard_match?(left, right) - left.end_with?(WILDCARD) && right.start_with?(left[WILDCARD_RANGE]) - end - end # Repository - end # Subscription - end # License -end # Mutant diff --git a/lib/mutant/version.rb b/lib/mutant/version.rb index 61cb51dd0..d3548b251 100644 --- a/lib/mutant/version.rb +++ b/lib/mutant/version.rb @@ -2,5 +2,5 @@ module Mutant # Current mutant version - VERSION = '0.11.34' + VERSION = '0.12.0' end # Mutant diff --git a/spec/unit/mutant/cli_spec.rb b/spec/unit/mutant/cli_spec.rb index 90cec8900..3d0f376de 100644 --- a/spec/unit/mutant/cli_spec.rb +++ b/spec/unit/mutant/cli_spec.rb @@ -84,19 +84,6 @@ def apply end end - shared_context 'license validation' do - let(:license_validation_event) do - [:license, :call, world] - end - - before do - allow(Mutant::License).to receive(:call) do |world| - events << [:license, :call, world] - license_result - end - end - end - @tests = [] @test_klass = @@ -117,7 +104,7 @@ def self.make # rubocop:disable Metrics/MethodLength def self.main_body <<~MESSAGE.strip - usage: mutant [options] + usage: mutant [options] Summary: mutation testing engine main command @@ -132,10 +119,9 @@ def self.main_body Available subcommands: - run - Run code analysis - environment - Environment subcommands - subscription - Subscription subcommands - util - Utility subcommands + run - Run code analysis + environment - Environment subcommands + util - Utility subcommands MESSAGE end # rubocop:enable Metrics/MethodLength @@ -218,11 +204,11 @@ def self.main_body context 'missing required subcommand' do message = <<~MESSAGE - mutant subscription: Missing required subcommand! + mutant environment: Missing required subcommand! - usage: mutant subscription [options] + usage: mutant environment [options] - Summary: Subscription subcommands + Summary: Environment subcommands mutant version: #{Mutant::VERSION} @@ -235,21 +221,13 @@ def self.main_body Available subcommands: - show - Show subscription status - test - Silently validates subscription, exits accordingly + subject - Subject subcommands + show - Display environment without coverage analysis + irb - Run irb with mutant environment loaded + test - test subcommands MESSAGE - let(:arguments) { %w[subscription] } - - it 'returns expected message' do - expect(apply).to eql(left(message)) - end - end - - context 'unexpected extra argument' do - message = 'mutant subscription show: Does not expect extra arguments' - - let(:arguments) { %w[subscription show extra-argument] } + let(:arguments) { %w[environment] } it 'returns expected message' do expect(apply).to eql(left(message)) @@ -258,9 +236,9 @@ def self.main_body make do message = <<~MESSAGE - usage: mutant subscription show [options] + usage: mutant environment irb [options] - Summary: Show subscription status + Summary: Run irb with mutant environment loaded mutant version: #{Mutant::VERSION} @@ -270,10 +248,39 @@ def self.main_body --version Print mutants version --profile Profile mutant execution --zombie Run mutant zombified + + + Environment: + -I, --include DIRECTORY Add DIRECTORY to $LOAD_PATH + -r, --require NAME Require file with NAME + --env KEY=VALUE Set environment variable + + + Runner: + --fail-fast Fail fast + -j, --jobs NUMBER Number of kill jobs. Defaults to number of processors. + -t, --mutation-timeout NUMBER Per mutation analysis timeout + + + Integration: + --use INTEGRATION deprecated alias for --integration + --integration NAME Use test integration with NAME + --integration-argument ARGUMENT + Pass ARGUMENT to integration + + + Matcher: + --ignore-subject EXPRESSION Ignore subjects that match EXPRESSION as prefix + --start-subject EXPRESSION Start mutation testing at a specific subject + --since REVISION Only select subjects touched since REVISION + + + Reporting: + --print-warnings Print warnings MESSAGE { - arguments: %w[subscription show --help], + arguments: %w[environment irb --help], expected_events: [[:stdout, :puts, message]], expected_exit: true, expected_print_profile: false, @@ -625,84 +632,11 @@ def self.main_body end end - context 'subscription show' do - let(:arguments) { %w[subscription show] } - - include_context 'license validation' - - context 'on valid license' do - let(:expected_exit) { true } - let(:license_result) { right(subscription) } - - let(:expected_events) do - [ - license_validation_event, - [:stdout, :puts, 'License-Description'] - ] - end - - let(:subscription) do - instance_double( - Mutant::License::Subscription, - description: 'License-Description' - ) - end - - include_examples 'CLI run' - end - - context 'on invalid license' do - let(:expected_exit) { false } - let(:license_result) { left('error-message') } - - let(:expected_events) do - [ - license_validation_event, - [:stderr, :puts, 'error-message'] - ] - end - - include_examples 'CLI run' - end - end - - context 'license display test' do - let(:arguments) { %w[subscription test] } - - let(:expected_events) do - [ [:license, :call, world ] ] - end - - include_context 'license validation' - - context 'on valid license' do - let(:expected_exit) { true } - let(:license_result) { right(subscription) } - - let(:subscription) do - instance_double( - Mutant::License::Subscription, - description: 'License-Description' - ) - end - - include_examples 'CLI run' - end - - context 'on invalid license' do - let(:expected_exit) { false } - let(:license_result) { left('error-message') } - - include_examples 'CLI run' - end - end - shared_context 'environment' do let(:arguments) { %w[run] } let(:bootstrap_result) { right(env) } let(:env_result) { instance_double(Mutant::Result::Env, success?: true) } let(:expected_exit) { true } - let(:license_result) { right(subscription) } let(:runner_result) { right(env_result) } let(:subjects) { [subject_a] } @@ -759,8 +693,6 @@ def self.main_body ) end - include_context 'license validation' - before do allow(Mutant::Config).to receive(:load) do |**attributes| events << [:load_config, attributes.inspect] @@ -1092,53 +1024,56 @@ def self.main_body context 'run' do include_context 'environment' - context 'on invalid license' do - let(:expected_exit) { false } - let(:license_result) { left('license-error') } - - let(:expected_events) do + let(:expected_events) do + [ + %i[ + record + config + ], + [ + :load_config, + { cli_config: expected_cli_config, world: world }.inspect + ], + [ + :bootstrap, + Mutant::Env.empty(world, bootstrap_config).inspect + ], [ - license_validation_event, - [:stderr, :puts, 'license-error'] + :runner, + env.inspect ] - end - - include_examples 'CLI run' + ] end - context 'on valid license' do - let(:subscription) do - instance_double( - Mutant::License::Subscription, - description: 'License-Description' - ) - end + context 'on runner fail' do + let(:expected_exit) { false } + let(:runner_result) { left('runner failure') } let(:expected_events) do [ - license_validation_event, - %i[ - record - config - ], - [ - :load_config, - { cli_config: expected_cli_config, world: world }.inspect - ], - [ - :bootstrap, - Mutant::Env.empty(world, bootstrap_config).inspect - ], + *super(), [ - :runner, - env.inspect + :stderr, + :puts, + 'runner failure' ] ] end - context 'on runner fail' do - let(:expected_exit) { false } - let(:runner_result) { left('runner failure') } + include_examples 'CLI run' + end + + context 'on runner success with unsuccessful result' do + context 'on alive mutations' do + let(:expected_exit) { false } + let(:runner_result) { right(env_result) } + + let(:env_result) do + instance_double( + Mutant::Result::Env, + success?: false + ) + end let(:expected_events) do [ @@ -1146,7 +1081,7 @@ def self.main_body [ :stderr, :puts, - 'runner failure' + 'Uncovered mutations detected, exiting nonzero!' ] ] end @@ -1154,245 +1089,218 @@ def self.main_body include_examples 'CLI run' end - context 'on runner success with unsuccessful result' do - context 'on alive mutations' do - let(:expected_exit) { false } - let(:runner_result) { right(env_result) } + context 'on not having found tests' do + let(:available_tests) { [] } - let(:env_result) do - instance_double( - Mutant::Result::Env, - success?: false - ) - end - - let(:expected_events) do + let(:expected_events) do + [ + *super()[..-2], [ - *super(), - [ - :stderr, - :puts, - 'Uncovered mutations detected, exiting nonzero!' - ] + :stderr, + :puts, + Mutant::CLI::Command::Environment::Run::NO_TESTS_MESSAGE ] - end - - include_examples 'CLI run' + ] end - context 'on not having found tests' do - let(:available_tests) { [] } + let(:expected_exit) { false } - let(:expected_events) do - [ - *super()[..-2], - [ - :stderr, - :puts, - Mutant::CLI::Command::Environment::Run::NO_TESTS_MESSAGE - ] - ] - end + include_examples 'CLI run' + end + end - let(:expected_exit) { false } + context 'with valid subject expression' do + let(:arguments) { super() + ['CLISubject'] } - include_examples 'CLI run' - end + let(:expected_cli_config) do + super().with( + matcher: super().matcher.with( + subjects: expected_bootstrap_subjects + ) + ) end - context 'with valid subject expression' do - let(:arguments) { super() + ['CLISubject'] } + let(:expected_bootstrap_subjects) { [parse_expression('CLISubject')] } - let(:expected_cli_config) do - super().with( - matcher: super().matcher.with( - subjects: expected_bootstrap_subjects - ) + include_examples 'CLI run' + end + + context 'without subject expressions' do + let(:expected_cli_config) do + super().with( + matcher: super().matcher.with( + subjects: expected_bootstrap_subjects ) - end + ) + end - let(:expected_bootstrap_subjects) { [parse_expression('CLISubject')] } + let(:expected_bootstrap_subjects) { [] } - include_examples 'CLI run' + include_examples 'CLI run' + end + + context 'with valid start-subject expression' do + let(:arguments) do + super() + ['--start-subject', 'Foo#bar', '--start-subject', 'Foo#baz'] end - context 'without subject expressions' do - let(:expected_cli_config) do - super().with( - matcher: super().matcher.with( - subjects: expected_bootstrap_subjects - ) + let(:expected_cli_config) do + super().with( + matcher: super().matcher.with( + start_expressions: %w[Foo#bar Foo#baz].map(&method(:parse_expression)) ) - end + ) + end - let(:expected_bootstrap_subjects) { [] } + include_examples 'CLI run' + end - include_examples 'CLI run' + context 'with valid ignore-subject expression' do + let(:arguments) do + super() + ['--ignore-subject', 'Foo#bar', '--ignore-subject', 'Foo#baz'] end - context 'with valid start-subject expression' do - let(:arguments) do - super() + ['--start-subject', 'Foo#bar', '--start-subject', 'Foo#baz'] - end - - let(:expected_cli_config) do - super().with( - matcher: super().matcher.with( - start_expressions: %w[Foo#bar Foo#baz].map(&method(:parse_expression)) - ) + let(:expected_cli_config) do + super().with( + matcher: super().matcher.with( + ignore: %w[Foo#bar Foo#baz].map(&method(:parse_expression)) ) - end - - include_examples 'CLI run' + ) end - context 'with valid ignore-subject expression' do - let(:arguments) do - super() + ['--ignore-subject', 'Foo#bar', '--ignore-subject', 'Foo#baz'] - end - - let(:expected_cli_config) do - super().with( - matcher: super().matcher.with( - ignore: %w[Foo#bar Foo#baz].map(&method(:parse_expression)) - ) - ) - end + include_examples 'CLI run' + end - include_examples 'CLI run' + context 'with --include option' do + let(:arguments) do + super() + %w[ + --include include-cli-a + --include include-cli-b + ] end - context 'with --include option' do - let(:arguments) do - super() + %w[ - --include include-cli-a - --include include-cli-b + let(:expected_cli_config) do + super().with( + includes: %w[ + include-cli-a + include-cli-b ] - end + ) + end - let(:expected_cli_config) do - super().with( - includes: %w[ - include-cli-a - include-cli-b - ] - ) - end + include_examples 'CLI run' + end - include_examples 'CLI run' + context 'with --require option' do + let(:arguments) { super() + %w[--require require-cli] } + + let(:expected_cli_config) do + super().with( + requires: %w[require-cli] + ) end - context 'with --require option' do - let(:arguments) { super() + %w[--require require-cli] } + include_examples 'CLI run' + end + + context 'with --env option' do + let(:arguments) { super() + %W[--env #{argument}] } + + context 'on valid env syntax' do + let(:argument) { 'foo=bar' } let(:expected_cli_config) do - super().with( - requires: %w[require-cli] - ) + super().with(environment_variables: { 'foo' => 'bar' }) end include_examples 'CLI run' end - context 'with --env option' do - let(:arguments) { super() + %W[--env #{argument}] } - - context 'on valid env syntax' do - let(:argument) { 'foo=bar' } + context 'on invalid env syntax' do + let(:argument) { 'foobar' } - let(:expected_cli_config) do - super().with(environment_variables: { 'foo' => 'bar' }) - end - - include_examples 'CLI run' + it 'raises expected error' do + expect { apply }.to raise_error(RuntimeError, 'Invalid env variable: "foobar"') end + end + end - context 'on invalid env syntax' do - let(:argument) { 'foobar' } + context 'with --jobs option' do + let(:arguments) { super() + %w[--jobs 10] } + let(:expected_cli_config) { super().with(jobs: 10) } - it 'raises expected error' do - expect { apply }.to raise_error(RuntimeError, 'Invalid env variable: "foobar"') - end - end - end + include_examples 'CLI run' + end - context 'with --jobs option' do - let(:arguments) { super() + %w[--jobs 10] } - let(:expected_cli_config) { super().with(jobs: 10) } + context 'with --mutation-timeout option' do + let(:arguments) { super() + %w[--mutation-timeout 10] } - include_examples 'CLI run' + let(:expected_cli_config) do + super().with(mutation: super().mutation.with(timeout: 10.0)) end - context 'with --mutation-timeout option' do - let(:arguments) { super() + %w[--mutation-timeout 10] } + include_examples 'CLI run' + end - let(:expected_cli_config) do - super().with(mutation: super().mutation.with(timeout: 10.0)) - end + context 'with --fail-fast option' do + let(:arguments) { super() + %w[--fail-fast] } + let(:expected_cli_config) { super().with(fail_fast: true) } - include_examples 'CLI run' - end + include_examples 'CLI run' + end - context 'with --fail-fast option' do - let(:arguments) { super() + %w[--fail-fast] } - let(:expected_cli_config) { super().with(fail_fast: true) } + context 'with --use option' do + let(:arguments) { super() + ['--use', 'cli-integration'] } - include_examples 'CLI run' + let(:expected_cli_config) do + super().with(integration: super().integration.with(name: 'cli-integration')) end - context 'with --use option' do - let(:arguments) { super() + ['--use', 'cli-integration'] } + include_examples 'CLI run' + end - let(:expected_cli_config) do - super().with(integration: super().integration.with(name: 'cli-integration')) - end + context 'with --integration option' do + let(:arguments) { super() + ['--integration', 'cli-integration'] } - include_examples 'CLI run' + let(:expected_cli_config) do + super().with(integration: super().integration.with(name: 'cli-integration')) end - context 'with --integration option' do - let(:arguments) { super() + ['--integration', 'cli-integration'] } - - let(:expected_cli_config) do - super().with(integration: super().integration.with(name: 'cli-integration')) - end + include_examples 'CLI run' + end - include_examples 'CLI run' + context 'with --integration-argument option' do + let(:arguments) do + super() + %w[ + --integration-argument cli-integration-argument-a + --integration-argument cli-integration-argument-b + ] end - context 'with --integration-argument option' do - let(:arguments) do - super() + %w[ - --integration-argument cli-integration-argument-a - --integration-argument cli-integration-argument-b - ] - end - - let(:expected_cli_config) do - super().with( - integration: super().integration.with( - arguments: %w[ - cli-integration-argument-a - cli-integration-argument-b - ] - ) + let(:expected_cli_config) do + super().with( + integration: super().integration.with( + arguments: %w[ + cli-integration-argument-a + cli-integration-argument-b + ] ) - end - - include_examples 'CLI run' + ) end - context 'with --since option' do - let(:arguments) { super() + ['--since', 'reference'] } + include_examples 'CLI run' + end - let(:expected_cli_config) do - diff = Mutant::Repository::Diff.new(to: 'reference', world: world) + context 'with --since option' do + let(:arguments) { super() + ['--since', 'reference'] } - super().with(matcher: super().matcher.with(diffs: [diff])) - end + let(:expected_cli_config) do + diff = Mutant::Repository::Diff.new(to: 'reference', world: world) - include_examples 'CLI run' + super().with(matcher: super().matcher.with(diffs: [diff])) end + + include_examples 'CLI run' end end end diff --git a/spec/unit/mutant/license/repository_spec.rb b/spec/unit/mutant/license/repository_spec.rb deleted file mode 100644 index 9803a9dd0..000000000 --- a/spec/unit/mutant/license/repository_spec.rb +++ /dev/null @@ -1,280 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe Mutant::License::Subscription::Repository do - def self.it_fails(expected) - it 'failse with exception' do - expect { apply }.to raise_error(expected) - end - end - - def self.it_is_successful - it 'returns expected repositories' do - expect(apply).to eql(right(repositories)) - end - end - - def self.it_fails_with_message(expected) - it 'returns expected message' do - expect(apply).to eql(left(expected)) - end - end - - describe '.parse' do - def apply - described_class.parse(input) - end - - let(:expected) do - described_class.new(host: 'github.com', path: 'mbj/mutant') - end - - context 'one to one' do - let(:input) { 'github.com/mbj/mutant' } - - it 'returns expected value' do - expect(apply).to eql(expected) - end - end - - context 'downcase' do - let(:input) { 'github.com/Mbj/Mutant' } - - it 'returns expected value' do - expect(apply).to eql(expected) - end - end - end - - describe '#to_s' do - def apply - described_class.new(host: 'github.com', path: 'mbj/mutant').to_s - end - - it 'returns expected value' do - expect(apply).to eql('github.com/mbj/mutant') - end - end - - describe '.load_from_git' do - def apply - described_class.load_from_git(world) - end - - let(:world) { instance_double(Mutant::World) } - - before do - allow(world).to receive(:capture_stdout, &commands.public_method(:fetch)) - end - - let(:git_remote_result) { right(git_remote) } - let(:allowed_repositories) { %w[github.com/mbj/mutant] } - - let(:git_remote) do - <<~REMOTE - origin\tgit@github.com:mbj/Mutant (fetch) - origin\tgit@github.com:mbj/Mutant (push) - REMOTE - end - - let(:commands) do - { - %w[git remote --verbose] => git_remote_result - } - end - - let(:repositories) do - [ - described_class.new( - host: 'github.com', - path: 'mbj/mutant' - ) - ].to_set - end - - context 'on different casing' do - let(:allowed_repositories) { %w[github.com/MBJ/MUTANT] } - - it_is_successful - end - - context 'on ssh url without protocol and without suffix' do - it_is_successful - end - - context 'on ssh url with protocol and without suffix' do - let(:git_remote) do - <<~REMOTE - origin\tssh://git@github.com/mbj/mutant (fetch) - origin\tssh://git@github.com/mbj/mutant (push) - REMOTE - end - - it_is_successful - end - - context 'on ssh url with protocol and suffix' do - let(:git_remote) do - <<~REMOTE - origin\tssh://git@github.com/mbj/mutant.git (fetch) - origin\tssh://git@github.com/mbj/mutant.git (push) - REMOTE - end - - it_is_successful - end - - context 'on https url without suffix' do - let(:git_remote) do - <<~REMOTE - origin\thttps://github.com/mbj/mutant (fetch) - origin\thttps://github.com/mbj/mutant (push) - REMOTE - end - - it_is_successful - end - - context 'on multiple different urls' do - let(:git_remote) do - <<~REMOTE - origin\thttps://github.com/mbj/mutant (fetch) - origin\thttps://github.com/mbj/mutant (push) - origin\thttps://github.com/mbj/unparser (fetch) - origin\thttps://github.com/mbj/unparser (push) - REMOTE - end - - let(:repositories) do - [ - described_class.new( - host: 'github.com', - path: 'mbj/mutant' - ), - described_class.new( - host: 'github.com', - path: 'mbj/unparser' - ) - ].to_set - end - - it_is_successful - end - - context 'on https url with .git suffix' do - let(:git_remote) do - <<~REMOTE - origin\thttps://github.com/mbj/mutant.git (fetch) - origin\thttps://github.com/mbj/mutant.git (push) - REMOTE - end - - it_is_successful - end - - context 'when git remote line cannot be parsed' do - let(:git_remote) { "some-bad-remote-line\n" } - - it_fails 'Unmatched remote line: "some-bad-remote-line\n"' - end - - context 'when git remote url cannot be parsed' do - let(:git_remote) { "some-unknown\thttp://github.com/mbj/mutant (fetch)\n" } - - it_fails 'Unmatched git remote URL: "http://github.com/mbj/mutant"' - end - end - - describe 'allow?' do - subject { described_class.new(host: 'github.com', path: 'mbj/mutant') } - - def self.it_returns_false - it 'returns false' do - expect(apply).to be(false) - end - end - - def self.it_returns_true - it 'returns false' do - expect(apply).to be(true) - end - end - - def apply - subject.allow?(other) - end - - context 'when repository is allowed' do - context 'via full match' do - let(:other) { described_class.new(host: 'github.com', path: 'mbj/mutant') } - - it_returns_true - end - - context 'via path pattern on right hand site' do - let(:other) { described_class.new(host: 'github.com', path: 'mbj/*') } - - it_returns_true - end - - context 'via path pattern on left hand site' do - subject { described_class.new(host: 'github.com', path: 'mbj/*') } - let(:other) { described_class.new(host: 'github.com', path: 'mbj/mutant') } - - it_returns_true - end - - context 'via path pattern on both sides' do - subject { described_class.new(host: 'github.com', path: 'mbj/*') } - let(:other) { described_class.new(host: 'github.com', path: 'mbj/*') } - - it_returns_true - end - end - - context 'when repository is not allowed' do - context 'on different host' do - let(:other) { described_class.new(host: 'gitlab.com', path: 'mbj/mutant') } - - it_returns_false - end - - context 'on different depository last empty' do - subject { described_class.new(host: 'github.com', path: 'b') } - - let(:other) { described_class.new(host: 'github.com', path: 'a') } - - it_returns_false - end - - context 'on different depository last chars' do - let(:other) { described_class.new(host: 'github.com', path: 'mbj/mutantb') } - - it_returns_false - end - - context 'on different depository partial overlap' do - let(:other) { described_class.new(host: 'github.com', path: 'mb/*') } - - it_returns_false - end - - context 'on different org path pattern' do - let(:other) { described_class.new(host: 'github.com', path: 'schirp-dso/*') } - - it_returns_false - end - - context 'on different org' do - let(:other) { described_class.new(host: 'github.com', path: 'schirp-dso/mutant') } - - it_returns_false - end - - context 'on different repo' do - let(:other) { described_class.new(host: 'github.com', path: 'mbj/unparser') } - - it_returns_false - end - end - end -end diff --git a/spec/unit/mutant/license/subscription_spec.rb b/spec/unit/mutant/license/subscription_spec.rb deleted file mode 100644 index ce014f472..000000000 --- a/spec/unit/mutant/license/subscription_spec.rb +++ /dev/null @@ -1,359 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe Mutant::License::Subscription do - describe '#description' do - def apply - object.description - end - - context 'on commercial license' do - context 'on individual license' do - let(:object) do - described_class::Commercial::Individual.new( - licensed: [ - described_class::Commercial::Individual::Author.new(email: 'mbj@schirp-dso.com'), - described_class::Commercial::Individual::Author.new(email: 'other@schirp-dso.com') - ].to_set - ) - end - - it 'returns expected description' do - expect(apply).to eql(<<~'MESSAGE') - commercial individual subscription: - Licensed: - mbj@schirp-dso.com - other@schirp-dso.com - MESSAGE - end - end - - context 'on organization license' do - let(:object) do - described_class::Commercial::Organization.new( - licensed: [ - described_class::Repository.new(host: 'github.com', path: 'mbj/*'), - described_class::Repository.new(host: 'github.com', path: 'schirp-dso/*') - ].to_set - ) - end - - it 'returns expected description' do - expect(apply).to eql(<<~'MESSAGE') - commercial organization subscription: - Licensed: - github.com/mbj/* - github.com/schirp-dso/* - MESSAGE - end - end - end - - context 'on opensource license' do - let(:object) do - described_class::Opensource.new( - licensed: [ - described_class::Repository.new(host: 'github.com', path: 'mbj/mutant'), - described_class::Repository.new(host: 'github.com', path: 'mbj/unparser') - ].to_set - ) - end - - it 'returns expected description' do - expect(apply).to eql(<<~'MESSAGE') - opensource repository subscription: - Licensed: - github.com/mbj/mutant - github.com/mbj/unparser - MESSAGE - end - end - end - - describe '.load' do - def apply - described_class.load(world, license_json) - end - - let(:world) { instance_double(Mutant::World) } - - before do - allow(world).to receive(:capture_stdout, &commands.public_method(:fetch)) - end - - def self.it_fails(expected) - it 'failse with exception' do - expect { apply }.to raise_error(expected) - end - end - - def self.it_is_successful - it 'allows usage' do - expect(apply).to eql(right(subscription)) - end - end - - def self.it_fails_with_message(expected) - it 'returns expected message' do - expect(apply).to eql(left(expected)) - end - end - - describe 'on opensource license' do - let(:git_remote_result) { right(git_remote) } - let(:allowed_repositories) { %w[github.com/mbj/mutant] } - - let(:license_json) do - { - 'type' => 'oss', - 'contents' => { - 'repositories' => allowed_repositories - } - } - end - - let(:commands) do - { - %w[git remote --verbose] => git_remote_result - } - end - - let(:git_remote) do - <<~REMOTE - origin\tgit@github.com:mbj/mutant (fetch) - origin\tgit@github.com:mbj/mutant (push) - REMOTE - end - - context 'on one of many match' do - let(:allowed_repositories) { %w[github.com/mbj/something github.com/mbj/mutant] } - - let(:git_remote) do - <<~REMOTE - origin\tgit@github.com:mbj/mutant (fetch) - origin\tgit@github.com:mbj/mutant (push) - origin\tgit@github.com:mbj/unparser (fetch) - origin\tgit@github.com:mbj/unparser (push) - REMOTE - end - - let(:subscription) do - Mutant::License::Subscription::Opensource.new( - licensed: [ - Mutant::License::Subscription::Repository.new( - host: 'github.com', - path: 'mbj/mutant' - ), - Mutant::License::Subscription::Repository.new( - host: 'github.com', - path: 'mbj/something' - ) - ].to_set - ) - end - it_is_successful - end - - context 'on direct match' do - let(:subscription) do - Mutant::License::Subscription::Opensource.new( - licensed: [ - Mutant::License::Subscription::Repository.new( - host: 'github.com', - path: 'mbj/mutant' - ) - ].to_set - ) - end - it_is_successful - end - - context 'when repository is not whitelisted' do - let(:allowed_repositories) { %w[gitlab.com/mbj/mutant] } - - it_fails_with_message(<<~'MESSAGE') - Can not validate opensource repository license. - Licensed: - gitlab.com/mbj/mutant - Present: - github.com/mbj/mutant - MESSAGE - end - end - - describe 'on commercial license' do - context 'on organization licenses' do - let(:git_remote_result) { right(git_remote) } - - let(:license_json) do - { - 'type' => 'com', - 'contents' => { - 'type' => 'organization', - 'repositories' => allowed_repositories - } - } - end - - let(:commands) do - { - %w[git remote --verbose] => git_remote_result - } - end - - let(:git_remote) do - <<~REMOTE - origin\tgit@github.com:mbj/Mutant (fetch) - origin\tgit@github.com:mbj/Mutant (push) - REMOTE - end - - let(:allowed_repositories) { %w[github.com/mbj/mutant] } - - context 'on a direct match' do - let(:subscription) do - Mutant::License::Subscription::Commercial::Organization.new( - licensed: [ - Mutant::License::Subscription::Repository.new( - host: 'github.com', - path: 'mbj/mutant' - ) - ].to_set - ) - end - - it_is_successful - end - - context 'on a one of many match' do - let(:allowed_repositories) { %w[github.com/mbj/something github.com/mbj/mutant] } - - let(:git_remote) do - <<~REMOTE - origin\tgit@github.com:mbj/mutant (fetch) - origin\tgit@github.com:mbj/mutant (push) - origin\tgit@github.com:mbj/unparser (fetch) - origin\tgit@github.com:mbj/unparser (push) - REMOTE - end - - let(:subscription) do - Mutant::License::Subscription::Commercial::Organization.new( - licensed: [ - Mutant::License::Subscription::Repository.new( - host: 'github.com', - path: 'mbj/mutant' - ), - Mutant::License::Subscription::Repository.new( - host: 'github.com', - path: 'mbj/something' - ) - ].to_set - ) - end - - it_is_successful - end - - context 'when repository is not whitelisted' do - let(:allowed_repositories) { %w[gitlab.com/mbj/mutant] } - - it_fails_with_message(<<~'MESSAGE') - Can not validate commercial organization license. - Licensed: - gitlab.com/mbj/mutant - Present: - github.com/mbj/mutant - MESSAGE - end - end - - shared_examples 'individual licenses' do - let(:git_config_author) { "customer-a@example.com\n" } - let(:git_config_result) { Mutant::Either::Right.new(git_config_author) } - let(:git_show_author) { "customer-b@example.com\n" } - let(:git_show_result) { Mutant::Either::Right.new(git_show_author) } - - let(:licensed_authors) do - %w[ - customer-a@example.com - customer-b@example.com - ] - end - - let(:commands) do - { - %w[git config --get user.email] => git_config_result, - %w[git show --quiet --pretty=format:%ae] => git_show_result - } - end - - let(:subscription) do - Mutant::License::Subscription::Commercial::Individual.new( - licensed: licensed_authors - .to_set { |email| Mutant::License::Subscription::Commercial::Individual::Author.new(email: email) } - ) - end - - context 'when author is whitelisted' do - it_is_successful - end - - context 'when author is not whitelisted' do - let(:licensed_authors) { %w[customer-c@example.com] } - - it_fails_with_message(<<~'MESSAGE') - Can not validate commercial individual license. - Licensed: - customer-c@example.com - Present: - customer-a@example.com - customer-b@example.com - MESSAGE - end - - context 'when author cannot be found in commit or config' do - let(:git_config_result) { Mutant::Either::Left.new('fatal: error') } - let(:git_show_result) { Mutant::Either::Left.new('fatal: error') } - - it_fails_with_message(<<~'MESSAGE') - Can not validate commercial individual license. - Licensed: - customer-a@example.com - customer-b@example.com - Present: - [none] - MESSAGE - end - end - - context 'on individual license' do - context 'via legacy contents missing type field' do - let(:license_json) do - { - 'type' => 'com', - 'contents' => { - 'authors' => licensed_authors - } - } - end - - include_examples 'individual licenses' - end - - context 'via contents type field' do - let(:license_json) do - { - 'type' => 'com', - 'contents' => { - 'type' => 'individual', - 'authors' => licensed_authors - } - } - end - - include_examples 'individual licenses' - end - end - end - end -end diff --git a/spec/unit/mutant/license_spec.rb b/spec/unit/mutant/license_spec.rb deleted file mode 100644 index 7800bae69..000000000 --- a/spec/unit/mutant/license_spec.rb +++ /dev/null @@ -1,125 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe Mutant::License do - def apply - described_class.call(world) - end - - let(:gem) { class_double(Gem, loaded_specs: loaded_specs) } - let(:gem_method) { instance_double(Method) } - let(:gem_path) { '/path/to/mutant-license' } - let(:gem_pathname) { instance_double(Pathname) } - let(:json) { class_double(JSON) } - let(:kernel) { class_double(Kernel) } - let(:license_json) { instance_double(Object) } - let(:license_pathname) { instance_double(Pathname) } - let(:load_json) { true } - let(:loaded_specs) { { 'mutant-license' => spec } } - let(:pathname) { class_double(Pathname) } - let(:stderr) { instance_double(IO) } - let(:subscription) { instance_double(Mutant::License::Subscription) } - let(:subscription_result) { right(subscription) } - - let(:spec) do - instance_double( - Gem::Specification, - full_gem_path: gem_path - ) - end - - let(:world) do - instance_double( - Mutant::World, - gem: gem, - gem_method: gem_method, - json: json, - kernel: kernel, - pathname: pathname, - stderr: stderr - ) - end - - before do - allow(gem_method).to receive_messages(call: undefined) - allow(gem_pathname).to receive_messages(join: license_pathname) - allow(json).to receive_messages(load: license_json) - allow(kernel).to receive_messages(sleep: undefined) - allow(pathname).to receive_messages(new: gem_pathname) - allow(Mutant::License::Subscription).to receive_messages(load: subscription_result) - end - - shared_examples 'license load' do - it 'performs IO in expected sequence' do - apply - - expect(gem_method) - .to have_received(:call) - .with('mutant-license', '>= 0.1', '< 0.3') - .ordered - - if load_json - expect(json) - .to have_received(:load) - .with(license_pathname) - .ordered - end - end - - it 'builds correct license.json path' do - if load_json - apply - - expect(pathname).to have_received(:new).with(gem_path) - expect(gem_pathname).to have_received(:join).with('license.json') - end - end - - it 'loads license json' do - if load_json - apply - - expect(Mutant::License::Subscription) - .to have_received(:load) - .with(world, license_json) - end - end - - it 'returns expected result' do - expect(apply).to eql(expected_result) - end - end - - def self.it_fails_with_message(message) - let(:expected_result) { left(message) } - - include_examples 'license load' - end - - context 'on successful license load' do - include_examples 'license load' - - let(:expected_result) { right(subscription) } - end - - context 'when mutant-license gem cannot be loaded' do - let(:load_json) { false } - - def self.setup_error(message) - before do - allow(gem_method).to receive(:call).and_raise(Gem::LoadError, message) - end - end - - context 'while the mutant license gem from rubygems is present' do - setup_error %{can't activate mutant-license (~> 0.1.0), already activated mutant-license-0.0.0.} - - it_fails_with_message 'mutant-license gem from rubygems.org is a dummy' - end - - context 'with other error message' do - setup_error 'test-error' - - it_fails_with_message 'test-error' - end - end -end diff --git a/test_app/Gemfile.rspec3.10.lock b/test_app/Gemfile.rspec3.10.lock index f9803f363..82654b075 100644 --- a/test_app/Gemfile.rspec3.10.lock +++ b/test_app/Gemfile.rspec3.10.lock @@ -1,21 +1,16 @@ PATH remote: .. specs: - mutant (0.11.34) + mutant (0.12.0) diff-lcs (~> 1.3) parser (~> 3.3.0) regexp_parser (~> 2.9.0) sorbet-runtime (~> 0.5.0) unparser (~> 0.6.9) - mutant-rspec (0.11.34) - mutant (= 0.11.34) + mutant-rspec (0.12.0) + mutant (= 0.12.0) rspec-core (>= 3.8.0, < 4.0.0) -GEM - remote: https://oss:Px2ENN7S91OmWaD5G7MIQJi1dmtmYrEh@gem.mutant.dev/ - specs: - mutant-license (0.1.1.2.2355046999240944981729280251890364410689.5) - GEM remote: https://rubygems.org/ specs: @@ -57,7 +52,6 @@ PLATFORMS DEPENDENCIES adamantium mutant! - mutant-license! mutant-rspec! rspec (~> 3.10) rspec-core (~> 3.10) diff --git a/test_app/Gemfile.rspec3.11.lock b/test_app/Gemfile.rspec3.11.lock index 2f95d523c..a8edd3186 100644 --- a/test_app/Gemfile.rspec3.11.lock +++ b/test_app/Gemfile.rspec3.11.lock @@ -1,21 +1,16 @@ PATH remote: .. specs: - mutant (0.11.34) + mutant (0.12.0) diff-lcs (~> 1.3) parser (~> 3.3.0) regexp_parser (~> 2.9.0) sorbet-runtime (~> 0.5.0) unparser (~> 0.6.9) - mutant-rspec (0.11.34) - mutant (= 0.11.34) + mutant-rspec (0.12.0) + mutant (= 0.12.0) rspec-core (>= 3.8.0, < 4.0.0) -GEM - remote: https://oss:Px2ENN7S91OmWaD5G7MIQJi1dmtmYrEh@gem.mutant.dev/ - specs: - mutant-license (0.1.1.2.2355046999240944981729280251890364410689.5) - GEM remote: https://rubygems.org/ specs: @@ -57,7 +52,6 @@ PLATFORMS DEPENDENCIES adamantium mutant! - mutant-license! mutant-rspec! rspec (~> 3.12) rspec-core (~> 3.12) diff --git a/test_app/Gemfile.rspec3.12.lock b/test_app/Gemfile.rspec3.12.lock index 62f1f59bf..94551d794 100644 --- a/test_app/Gemfile.rspec3.12.lock +++ b/test_app/Gemfile.rspec3.12.lock @@ -1,21 +1,16 @@ PATH remote: .. specs: - mutant (0.11.34) + mutant (0.12.0) diff-lcs (~> 1.3) parser (~> 3.3.0) regexp_parser (~> 2.9.0) sorbet-runtime (~> 0.5.0) unparser (~> 0.6.9) - mutant-rspec (0.11.34) - mutant (= 0.11.34) + mutant-rspec (0.12.0) + mutant (= 0.12.0) rspec-core (>= 3.8.0, < 4.0.0) -GEM - remote: https://oss:Px2ENN7S91OmWaD5G7MIQJi1dmtmYrEh@gem.mutant.dev/ - specs: - mutant-license (0.1.1.2.2355046999240944981729280251890364410689.5) - GEM remote: https://rubygems.org/ specs: @@ -57,7 +52,6 @@ PLATFORMS DEPENDENCIES adamantium mutant! - mutant-license! mutant-rspec! rspec (~> 3.9) rspec-core (~> 3.9) diff --git a/test_app/Gemfile.rspec3.13.lock b/test_app/Gemfile.rspec3.13.lock index 316642652..d290e750c 100644 --- a/test_app/Gemfile.rspec3.13.lock +++ b/test_app/Gemfile.rspec3.13.lock @@ -1,21 +1,16 @@ PATH remote: .. specs: - mutant (0.11.34) + mutant (0.12.0) diff-lcs (~> 1.3) parser (~> 3.3.0) regexp_parser (~> 2.9.0) sorbet-runtime (~> 0.5.0) unparser (~> 0.6.9) - mutant-rspec (0.11.34) - mutant (= 0.11.34) + mutant-rspec (0.12.0) + mutant (= 0.12.0) rspec-core (>= 3.8.0, < 4.0.0) -GEM - remote: https://oss:Px2ENN7S91OmWaD5G7MIQJi1dmtmYrEh@gem.mutant.dev/ - specs: - mutant-license (0.1.1.2.2355046999240944981729280251890364410689.5) - GEM remote: https://rubygems.org/ specs: @@ -57,7 +52,6 @@ PLATFORMS DEPENDENCIES adamantium mutant! - mutant-license! mutant-rspec! rspec (~> 3.13) rspec-core (~> 3.13) diff --git a/test_app/Gemfile.rspec3.8.lock b/test_app/Gemfile.rspec3.8.lock index fdabbed7f..c6804d163 100644 --- a/test_app/Gemfile.rspec3.8.lock +++ b/test_app/Gemfile.rspec3.8.lock @@ -1,21 +1,16 @@ PATH remote: .. specs: - mutant (0.11.34) + mutant (0.12.0) diff-lcs (~> 1.3) parser (~> 3.3.0) regexp_parser (~> 2.9.0) sorbet-runtime (~> 0.5.0) unparser (~> 0.6.9) - mutant-rspec (0.11.34) - mutant (= 0.11.34) + mutant-rspec (0.12.0) + mutant (= 0.12.0) rspec-core (>= 3.8.0, < 4.0.0) -GEM - remote: https://oss:Px2ENN7S91OmWaD5G7MIQJi1dmtmYrEh@gem.mutant.dev/ - specs: - mutant-license (0.1.1.2.2355046999240944981729280251890364410689.5) - GEM remote: https://rubygems.org/ specs: @@ -57,7 +52,6 @@ PLATFORMS DEPENDENCIES adamantium mutant! - mutant-license! mutant-rspec! rspec (~> 3.8) rspec-core (~> 3.8) diff --git a/test_app/Gemfile.rspec3.9.lock b/test_app/Gemfile.rspec3.9.lock index 62f1f59bf..94551d794 100644 --- a/test_app/Gemfile.rspec3.9.lock +++ b/test_app/Gemfile.rspec3.9.lock @@ -1,21 +1,16 @@ PATH remote: .. specs: - mutant (0.11.34) + mutant (0.12.0) diff-lcs (~> 1.3) parser (~> 3.3.0) regexp_parser (~> 2.9.0) sorbet-runtime (~> 0.5.0) unparser (~> 0.6.9) - mutant-rspec (0.11.34) - mutant (= 0.11.34) + mutant-rspec (0.12.0) + mutant (= 0.12.0) rspec-core (>= 3.8.0, < 4.0.0) -GEM - remote: https://oss:Px2ENN7S91OmWaD5G7MIQJi1dmtmYrEh@gem.mutant.dev/ - specs: - mutant-license (0.1.1.2.2355046999240944981729280251890364410689.5) - GEM remote: https://rubygems.org/ specs: @@ -57,7 +52,6 @@ PLATFORMS DEPENDENCIES adamantium mutant! - mutant-license! mutant-rspec! rspec (~> 3.9) rspec-core (~> 3.9)