Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'rubocop/cop/experiments_test_coverage.rb')
-rw-r--r--rubocop/cop/experiments_test_coverage.rb114
1 files changed, 114 insertions, 0 deletions
diff --git a/rubocop/cop/experiments_test_coverage.rb b/rubocop/cop/experiments_test_coverage.rb
new file mode 100644
index 00000000000..4bb2832030c
--- /dev/null
+++ b/rubocop/cop/experiments_test_coverage.rb
@@ -0,0 +1,114 @@
+# frozen_string_literal: true
+
+module RuboCop
+ module Cop
+ # Check for test coverage for GitLab experiments.
+ class ExperimentsTestCoverage < RuboCop::Cop::Base
+ CLASS_OFFENSE = 'Make sure experiment class has test coverage for all the variants.'
+ BLOCK_OFFENSE = 'Make sure experiment block has test coverage for all the variants.'
+
+ # Validates classes inherited from ApplicationExperiment
+ # These classes are located under app/experiments or ee/app/experiments
+ def on_class(node)
+ return if node.parent_class&.const_name != 'ApplicationExperiment'
+ return if covered_with_tests?(node)
+
+ add_offense(node, message: CLASS_OFFENSE)
+ end
+
+ # Validates experiments block in *.rb and *.haml files:
+ # experiment(:experiment_name) do |e|
+ # e.candidate { 'candidate' }
+ # e.run
+ # end
+ def on_block(node)
+ return if node.method_name != :experiment
+ return if covered_with_tests?(node)
+
+ add_offense(node, message: BLOCK_OFFENSE)
+ end
+
+ private
+
+ def covered_with_tests?(node)
+ tests_code = test_files_code(node)
+
+ return false if tests_code.blank?
+ return false unless tests_code.match?(stub_experiments_matcher)
+ return false unless tests_code.include?(experiment_name(node))
+
+ experiment_variants(node).map { |variant| tests_code.include?(variant) }.all?(&:present?)
+ end
+
+ def test_files_code(node)
+ # haml-lint add .rb extension to *.haml files
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/415330#caveats
+ test_file_path = filepath(node).gsub('app/', 'spec/').gsub('.rb', '_spec.rb')
+ "#{read_file(test_file_path)}\n#{additional_tests_code(test_file_path)}"
+ end
+
+ def additional_tests_code(test_file_path)
+ # rubocop:disable Gitlab/NoCodeCoverageComment
+ # :nocov: File paths stubed in tests
+ if test_file_path.include?('/controllers/')
+ read_file(test_file_path.gsub('/controllers/', '/requests/'))
+ elsif test_file_path.include?('/lib/api/')
+ read_file(test_file_path.gsub('/lib/', '/spec/requests/'))
+ end
+ # :nocov:
+ # rubocop:enable Gitlab/NoCodeCoverageComment
+ end
+
+ def read_file(file_path)
+ File.exist?(file_path) ? File.new(file_path).read : ''
+ end
+
+ def experiment_name(node)
+ if node.is_a?(RuboCop::AST::ClassNode)
+ File.basename(filepath(node), '_experiment.rb')
+ else
+ block_node_value(node)
+ end
+ end
+
+ def experiment_variants(node)
+ node.body.children.filter_map do |child|
+ next unless child.is_a?(RuboCop::AST::SendNode) || child.is_a?(RuboCop::AST::BlockNode)
+
+ extract_variant(child)
+ end
+ end
+
+ def extract_variant(node)
+ # control enabled by default for tests
+ case node.method_name
+ when :candidate then 'candidate'
+ when :variant then variant_name(node)
+ end
+ end
+
+ def variant_name(node)
+ return send_node_value(node) if node.is_a?(RuboCop::AST::SendNode)
+
+ block_node_value(node)
+ end
+
+ def block_node_value(node)
+ send_node_value(node.children[0])
+ end
+
+ def send_node_value(node)
+ node.children[2].value.to_s
+ end
+
+ def filepath(node)
+ node.location.expression.source_buffer.name
+ end
+
+ def stub_experiments_matcher
+ # validates test files contains uncommented stub_experiments(...
+ /^([^#]|\s*|\w*)stub_experiments\(/
+ end
+ end
+ end
+end