qat-cucumber 6.0.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.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +674 -0
  3. data/bin/qat +5 -0
  4. data/lib/qat/cli/generator/project.rb +32 -0
  5. data/lib/qat/cli/generator.rb +48 -0
  6. data/lib/qat/cli/main.rb +102 -0
  7. data/lib/qat/cli/plugins/core.rb +19 -0
  8. data/lib/qat/cli.rb +52 -0
  9. data/lib/qat/cucumber/core_ext/formatter/html.rb +48 -0
  10. data/lib/qat/cucumber/core_ext/formatter/junit.rb +57 -0
  11. data/lib/qat/cucumber/core_ext/result.rb +16 -0
  12. data/lib/qat/cucumber/core_ext.rb +3 -0
  13. data/lib/qat/cucumber/hooks/scenario.rb +76 -0
  14. data/lib/qat/cucumber/hooks.rb +72 -0
  15. data/lib/qat/cucumber/logger.rb +49 -0
  16. data/lib/qat/cucumber/time.rb +55 -0
  17. data/lib/qat/cucumber/version.rb +15 -0
  18. data/lib/qat/cucumber/world.rb +40 -0
  19. data/lib/qat/cucumber.rb +76 -0
  20. data/lib/qat/formatter/console.rb +101 -0
  21. data/lib/qat/formatter/dashboard.rb +84 -0
  22. data/lib/qat/formatter/loggable/mdc.rb +74 -0
  23. data/lib/qat/formatter/loggable/scenario_info.rb +40 -0
  24. data/lib/qat/formatter/loggable.rb +92 -0
  25. data/lib/qat/formatter/scenario/name.rb +47 -0
  26. data/lib/qat/formatter/tags.rb +81 -0
  27. data/lib/qat/formatter/test_ids.rb +93 -0
  28. data/lib/qat/jenkins.rb +43 -0
  29. data/lib/qat/project/Gemfile +13 -0
  30. data/lib/qat/project/Rakefile +3 -0
  31. data/lib/qat/project/config/cucumber.yml +13 -0
  32. data/lib/qat/project/config/default.yml +1 -0
  33. data/lib/qat/project/config/env-dummy/hosts.yml +9 -0
  34. data/lib/qat/project/config/env-dummy/jenkins.yml +7 -0
  35. data/lib/qat/project/config/env-dummy/logger.yml +23 -0
  36. data/lib/qat/project/config/env-dummy/time.yml +11 -0
  37. data/lib/qat/project/features/feature.feature +45 -0
  38. data/lib/qat/project/features/step_definitions/steps.rb +4 -0
  39. data/lib/qat/project/features/support/env.rb +6 -0
  40. data/lib/qat/project/features/support/hooks.rb +32 -0
  41. data/lib/qat/tasks/list.rb +50 -0
  42. data/lib/qat/tasks/steps.rb +20 -0
  43. data/lib/qat/tasks/tags/test_ids/helpers.rb +105 -0
  44. data/lib/qat/tasks/tags/test_ids/report.rb +41 -0
  45. data/lib/qat/tasks/tags/test_ids.rb +60 -0
  46. data/lib/qat/tasks/tags.rb +2 -0
  47. data/lib/qat/tasks/test.rb +35 -0
  48. data/lib/qat/tasks.rb +8 -0
  49. metadata +193 -0
@@ -0,0 +1,40 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'minitest'
3
+ require 'cucumber'
4
+ require_relative 'version'
5
+ require 'qat/logger'
6
+
7
+ module QAT
8
+ module Cucumber
9
+ #Cucumber World utility. Will be automatically included in the World object when this file is required.
10
+ #Includes MiniTest and a Logger utility.
11
+ #In order to define the Logger channel, a World Class should be defined by the user.
12
+ #Should be required in the env.rb file.
13
+ module World
14
+ include QAT::Logger
15
+ include Minitest::Assertions
16
+
17
+ attr_accessor :assertions
18
+
19
+ # @!attribute assertions
20
+ # @return [Integer] Counter of assertions for Minitest integration.
21
+ def assertions
22
+ @assertions ||= 0
23
+ end
24
+
25
+ def test_id
26
+ QAT[:current_test_id]
27
+ end
28
+
29
+ def test_run_id
30
+ QAT[:current_test_run_id]
31
+ end
32
+
33
+ def evidence_prefix
34
+ QAT[:current_test_run_id]
35
+ end
36
+
37
+ end
38
+ end
39
+ end
40
+
@@ -0,0 +1,76 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'minitest'
3
+ require 'cucumber'
4
+ require_relative 'cucumber/version'
5
+ require_relative 'cucumber/core_ext'
6
+ require 'qat/logger'
7
+ require 'qat/core'
8
+ require 'qat/configuration'
9
+ require 'qat/time'
10
+ require_relative 'jenkins' if ENV['JENKINS_URL']
11
+ require_relative 'cucumber/logger'
12
+ require_relative 'cucumber/time'
13
+
14
+ require_relative 'cucumber/hooks'
15
+ require_relative 'cucumber/world'
16
+ World QAT::Cucumber::World
17
+
18
+ #QAT Module works as a namespace for all sub modules.
19
+ #Some singleton methods are also available, as defined by various sub classes.
20
+ #@since 0.1.0
21
+ module QAT
22
+ # Namespace for various helpers when running with cucumber.
23
+ #Just require 'qat/cucumber' to automatically integrate all the available helpers.
24
+ #
25
+ #@since 0.1.0
26
+ module Cucumber
27
+ include QAT::Logger
28
+
29
+ class << self
30
+ include QAT::Cucumber::Logger
31
+ include QAT::Cucumber::Time
32
+
33
+ # Launches the pre-test configurations and integrations
34
+ # This includes:
35
+ # - Time Synchronization between the host running tests
36
+ # and a target host (test environment?) if configured
37
+ # - Setups the Jenkins integration if running this CI Server
38
+ def launch!
39
+ current_configuration = QAT.configuration
40
+
41
+ raise EmptyConfiguration.new "No valid configuration exists to run tests!" unless current_configuration
42
+ raise InvalidConfiguration.new "No valid environment is defined, aborting test execution!" unless current_configuration.environment
43
+
44
+ config_logger(current_configuration)
45
+
46
+ time_sync if current_configuration[:time]
47
+
48
+ setup_jenkins((current_configuration)) if ENV['JENKINS_URL']
49
+ end
50
+
51
+ private
52
+
53
+ # Initializes the Jenkins configuration for test run
54
+ # @param configuration [Hash] configuration
55
+ def setup_jenkins(configuration)
56
+ jenkins_vars = configuration.dig(:jenkins, :env_vars)
57
+
58
+ if jenkins_vars
59
+ QAT::Jenkins.register_vars(jenkins_vars)
60
+ else
61
+ log.debug { "Configuring Jenkins with default options." }
62
+ QAT::Jenkins.register_vars
63
+ end
64
+ end
65
+ end
66
+
67
+ # This class represents a empty configuration error when there is no configuration available
68
+ class EmptyConfiguration < StandardError
69
+ end
70
+ # This class represents a invalid configuration error when the configuration is not complete
71
+ class InvalidConfiguration < StandardError
72
+ end
73
+ end
74
+ end
75
+
76
+ QAT::Cucumber.launch!
@@ -0,0 +1,101 @@
1
+ require 'fileutils'
2
+ require 'cucumber/formatter/console'
3
+ require 'cucumber/formatter/io'
4
+ require 'cucumber/gherkin/formatter/escaping'
5
+ require 'qat/logger'
6
+ require_relative 'loggable'
7
+
8
+ module QAT
9
+ module Formatter
10
+ # Formatter to print Feature, Scenario and Step information on the fly. Will use STDOUT by default or a specified
11
+ # logger configuration channel.
12
+ #@see QAT::Loggger
13
+ #@since 0.1.0
14
+ class Console
15
+ include ::FileUtils
16
+ include ::Cucumber::Formatter::Io
17
+ include ::Cucumber::Gherkin::Formatter::Escaping
18
+ include QAT::Formatter::Loggable
19
+ include QAT::Logger
20
+
21
+ #@api private
22
+ def initialize(_, path_or_io, options)
23
+ @options = options
24
+
25
+ check_outputter path_or_io unless options[:dry_run]
26
+ end
27
+
28
+ #@api private
29
+ def before_test_case test_case
30
+ return if @options[:dry_run]
31
+
32
+ unless @current_feature
33
+ @current_feature = test_case.source[0]
34
+ log.info { "Running #{@current_feature.keyword}: \"#{@current_feature.name}\"" }
35
+ mdc_before_feature! @current_feature.name
36
+ end
37
+
38
+ @current_scenario = test_case.source[1]
39
+ end
40
+
41
+ #@api private
42
+ def after_feature *_
43
+ return if @options[:dry_run]
44
+
45
+ log.info { "Finished #{@current_feature.keyword}: \"#{@current_feature.name}\"" }
46
+ @current_feature = nil
47
+ mdc_after_feature!
48
+ end
49
+
50
+ #@api private
51
+ def after_test_case step, result
52
+ return if @options[:dry_run]
53
+
54
+ log.error { result.exception } if result.failed?
55
+
56
+ log.info { "Finished #{@current_scenario.keyword}: \"#{format_scenario_name step}\" - #{result.to_sym}\n" } if @current_scenario
57
+ end
58
+
59
+ #@api private
60
+ def before_test_step step
61
+ return if @options[:dry_run]
62
+
63
+ begin_test_step step do |type|
64
+ case type
65
+ when :after_step
66
+ log.info "Step Done!" if @step_running
67
+ when :before_scenario
68
+ log.info { "Running #{@current_scenario.keyword}: \"#{format_scenario_name step}\"" }
69
+ when :before_step
70
+ log.info "Step Done!\n" if @step_running
71
+ step_name = "#{step.source.last.keyword}#{step.name}"
72
+ log.info { "Step \"#{step_name}\"" }
73
+ mdc_add_step! step_name
74
+ end
75
+ end
76
+ end
77
+
78
+ #@api private
79
+ def puts obj
80
+ return if @options[:dry_run]
81
+
82
+ log.debug { obj }
83
+ end
84
+
85
+ private
86
+ def format_scenario_name step
87
+ return '' unless @current_scenario
88
+ outline_number, outline_example = nil, nil
89
+ scenario_name = if @current_scenario.is_a? ::Cucumber::Core::Ast::ScenarioOutline
90
+ outline_example = step.source[3].values
91
+ outline_number = calculate_outline_id(step)
92
+ "#{@current_scenario.name} ##{outline_number}"
93
+ else
94
+ @current_scenario.name
95
+ end
96
+ mdc_before_scenario! @current_scenario.name, tags_from_test_step(step), outline_number, outline_example
97
+ return scenario_name
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,84 @@
1
+ require 'fileutils'
2
+ require 'cucumber/formatter/console'
3
+ require 'cucumber/formatter/io'
4
+ require 'cucumber/gherkin/formatter/escaping'
5
+ require 'qat/logger'
6
+ require_relative 'loggable'
7
+
8
+ module QAT
9
+ module Formatter
10
+ # Formatter to send error information to a Dashboard server. Output should be configured in the logger file.
11
+ #@see QAT::Loggger
12
+ #@since 0.1.0
13
+ class Dashboard
14
+ include ::FileUtils
15
+ include ::Cucumber::Formatter::Io
16
+ include ::Cucumber::Gherkin::Formatter::Escaping
17
+ include QAT::Formatter::Loggable
18
+ include QAT::Logger
19
+
20
+ #@api private
21
+ def initialize(_, path_or_io, options)
22
+ @options = options
23
+
24
+ ensure_outputter path_or_io unless options[:dry_run]
25
+ end
26
+
27
+
28
+ #@api private
29
+ def before_test_case test_case
30
+ return if @options[:dry_run]
31
+
32
+ unless @current_feature
33
+ @current_feature = test_case.source[0]
34
+ mdc_before_feature! @current_feature.name
35
+ end
36
+
37
+ @current_scenario = test_case.source[1]
38
+ end
39
+
40
+ #@api private
41
+ def after_feature *_
42
+ return if @options[:dry_run]
43
+
44
+ @current_feature = nil
45
+ mdc_after_feature!
46
+ end
47
+
48
+ #@api private
49
+ def after_test_case step, passed
50
+ return if @options[:dry_run]
51
+
52
+ if passed.respond_to? :exception
53
+ mdc_add_step! @step_name
54
+ mdc_add_status_failed!
55
+ log.error passed.exception
56
+ end
57
+ end
58
+
59
+ #@api private
60
+ def before_test_step step
61
+ return if @options[:dry_run]
62
+
63
+ begin_test_step step do |type|
64
+ if type == :before_step
65
+ @step_name = "#{step.source.last.keyword}#{step.name}"
66
+ mdc_add_step! @step_name
67
+ elsif :before_scenario
68
+ outline_number, outline_example = nil, nil
69
+ if @current_scenario.is_a? ::Cucumber::Core::Ast::ScenarioOutline
70
+ outline_example = step.source[3].values
71
+ outline_number = calculate_outline_id(step)
72
+ end
73
+ mdc_before_scenario! @current_scenario.name, tags_from_test_step(step), outline_number, outline_example
74
+ end
75
+ end
76
+ end
77
+
78
+ # #@api private
79
+ # def exception e, _
80
+ # log.error e
81
+ # end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,74 @@
1
+ require 'qat/logger'
2
+ require_relative 'scenario_info'
3
+
4
+ module QAT
5
+ module Formatter
6
+ module Loggable
7
+ #Helper module to manage loggable mdc information
8
+ #@since 2.0.2
9
+ module Mdc
10
+ include ScenarioInfo
11
+
12
+ #Set the MDC key 'feature' with the given value.
13
+ #@param feature_name [String] Name of the current feature
14
+ #@since 0.1.0
15
+ def mdc_before_feature!(feature_name)
16
+ Log4r::MDC.put 'feature', feature_name
17
+ end
18
+
19
+ #Remove the MDC key 'feature'.
20
+ #@since 0.1.0
21
+ def mdc_after_feature!
22
+ Log4r::MDC.remove 'feature'
23
+ end
24
+
25
+ #Set the MDC key 'scenario' with the given value.
26
+ #Optionally scenario outline data can also be provided
27
+ #@param scenario_name [String] Name of the current feature
28
+ #@param tags [Array<String>] List of tags
29
+ #@param outline_number [Integer] Number of the current scenario outline
30
+ #@param outline_example [Array<String>] Values of the current outline's example row
31
+ #@since 0.1.0
32
+ def mdc_before_scenario!(scenario_name, tags, outline_number = nil, outline_example = nil)
33
+ mdc_reset_scenario!
34
+ Log4r::MDC.put 'scenario', scenario_name
35
+
36
+ test_id = test_id(tags, outline_number)
37
+ Log4r::MDC.put 'test_id', test_id
38
+ loggable_tags = tags.reject { |tag| tag.match(/^\@test\#/) }
39
+ Log4r::MDC.put 'tags', loggable_tags
40
+
41
+ Log4r::MDC.put 'outline_number', outline_number if outline_number
42
+ Log4r::MDC.put 'outline_example', outline_example if outline_example
43
+ end
44
+
45
+ #Remove the MDC key 'scenario'. Other options set in #mdc_before_scenario! will also be unset.
46
+ #@since 0.1.0
47
+ def mdc_reset_scenario!
48
+ mdc_remove_step!
49
+ Log4r::MDC.remove 'scenario'
50
+ Log4r::MDC.remove 'tags'
51
+ Log4r::MDC.remove 'outline_number'
52
+ Log4r::MDC.remove 'outline_example'
53
+ end
54
+
55
+ #Set the MDC key 'step' with the given value.
56
+ #@param step_name [String] Name of the current step
57
+ #@since 0.1.0
58
+ def mdc_add_step!(step_name)
59
+ Log4r::MDC.put 'step', step_name
60
+ end
61
+
62
+ #Remove the MDC key 'step'.
63
+ #@since 0.1.0
64
+ def mdc_remove_step!
65
+ Log4r::MDC.remove 'step'
66
+ end
67
+
68
+ def mdc_add_status_failed!
69
+ Log4r::MDC.put 'status', 'failed'
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,40 @@
1
+ require 'qat/logger'
2
+
3
+ module QAT
4
+ module Formatter
5
+ module Loggable
6
+ #Helper module to manage loggable scenario information
7
+ #@since 2.0.2
8
+ module ScenarioInfo
9
+ protected
10
+
11
+ def tags_from_test_step(step)
12
+ step.source.inject([]) do |sum, current_source|
13
+ sum << current_source.tags if current_source.respond_to? :tags
14
+ sum
15
+ end.flatten.map(&:name)
16
+ end
17
+
18
+ def test_id(tags, outline_id)
19
+ tag = tags.select { |tag| tag.match /^\@test\#\d+$/ }.first
20
+ if tag
21
+ id = tag.gsub '@test#', 'test_'
22
+ id << ".#{outline_id}" if outline_id
23
+ id
24
+ else
25
+ nil
26
+ end
27
+ end
28
+
29
+ def calculate_outline_id(step)
30
+ source = step.source
31
+ tables = source[1].instance_exec { @examples_tables }
32
+ table_lines = tables.each.map { |line| line.example_rows.size }
33
+ table_num = tables.index source[2]
34
+ previous_outlines = table_lines[0...table_num].inject(0) { |sum, lines| sum += lines; sum }
35
+ "#{previous_outlines + source[3].number}".to_i
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,92 @@
1
+ require 'qat/logger'
2
+ require_relative 'loggable/mdc'
3
+
4
+ module QAT
5
+ #Namespace for custom Cucumber formatters and helpers.
6
+ #@since 0.1.0
7
+ module Formatter
8
+ #Helper module to manage formatters based on QAT::Logger
9
+ #@since 0.1.0
10
+ module Loggable
11
+ include QAT::Logger
12
+ include Mdc
13
+
14
+ def check_outputter name
15
+ return if name.respond_to? :write #Is an IO
16
+
17
+ add_outputter_with name
18
+ end
19
+
20
+
21
+ def ensure_outputter name
22
+ raise ArgumentError.new "No outputter configured for formatter #{self.class.name}" if name.respond_to? :write
23
+
24
+ add_outputter_with name
25
+ end
26
+
27
+ #Parses a test step and determines it's type: after step, before scenario or before step.
28
+ #Allows a block to be used to execute in the middle of the processing
29
+ #@param step [Cucumber::Core::Test::Step] Step to parse
30
+ #@yield [type] Block to execute in the middle of processing
31
+ #@yieldparam [Symbol] type Type of the test step
32
+ #@return [Symbol] type Type of the test step
33
+ #@since 0.1.0
34
+ def begin_test_step step
35
+ #World: step.location = /usr/local/rvm/gems/ruby-2.2.3/gems/cucumber-2.0.2/lib/cucumber/filters/prepare_world.rb:27
36
+ # step.name = "Before hook"
37
+
38
+ #Hooks: step.location = /home/mgomes/Projects/qat/src/qat/lib/qat/cucumber/hooks.rb:53
39
+ # step.name = "Before hook"
40
+
41
+ #Stepdef: step.location = features/formatter.feature:8
42
+ # step.name = step name
43
+
44
+ type = set_type(step)
45
+
46
+ yield type if block_given? and type
47
+
48
+ set_step_status(type)
49
+
50
+ type
51
+ end
52
+
53
+ private
54
+
55
+ def add_outputter_with(name)
56
+ require_relative '../cucumber'
57
+
58
+ outputter = Log4r::Outputter[name]
59
+
60
+ raise ArgumentError.new "No outputter in loaded configuration file with name #{name}" unless outputter
61
+
62
+ return if log.outputters.map(&:name).include? name
63
+
64
+ log.add outputter
65
+ end
66
+
67
+ def set_type(step)
68
+ location = step.location.file.to_s
69
+ step_name = step.name
70
+
71
+ if step_name == "After hook"
72
+ :after_step
73
+ elsif step_name == "Before hook" and location.end_with? 'prepare_world.rb'
74
+ @current_scenario = step.source[1]
75
+ :before_scenario
76
+ elsif File.extname(location.to_s) == '.feature'
77
+ :before_step
78
+ end
79
+ end
80
+
81
+ def set_step_status(type)
82
+ if type == :after_step
83
+ @step_running = false
84
+ elsif type == :before_scenario
85
+ @step_running = false
86
+ elsif type == :before_step
87
+ @step_running = true
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,47 @@
1
+ require 'cucumber/formatter/io'
2
+ require 'json'
3
+
4
+ module QAT
5
+ module Formatter
6
+ module Scenario
7
+ class Name
8
+ include Cucumber::Formatter::Io
9
+
10
+ def initialize(runtime, path_or_io, options)
11
+ @runtime = runtime
12
+ @io = ensure_io(path_or_io)
13
+ @to_file = (@io != $stdout)
14
+ @options = options
15
+ @scenarios = {}
16
+ @repeated = {}
17
+ end
18
+
19
+ def scenario_name(keyword, name, file_colon_line, source_indent)
20
+ if @to_file
21
+ if @scenarios.values.include?(name)
22
+ @repeated[name] ||= []
23
+ @repeated[name] << file_colon_line
24
+ end
25
+ @scenarios[file_colon_line] = name
26
+ else
27
+ puts "#{name}: #{file_colon_line}"
28
+ end
29
+ end
30
+
31
+ def after_features(features)
32
+ if @to_file
33
+ content = {
34
+ scenarios: @scenarios,
35
+ repeated: @repeated
36
+ }
37
+ @io.puts(content.to_json({
38
+ indent: ' ',
39
+ space: ' ',
40
+ object_nl: "\n"
41
+ }))
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,81 @@
1
+ require 'cucumber/formatter/io'
2
+ require 'json'
3
+
4
+ module QAT
5
+ module Formatter
6
+ # This formatter prints test scenarios tags information to a JSON format.
7
+ # Information includes:
8
+ # - untagged test scenarios
9
+ # - list of unique tags used
10
+ # - total number of tags used
11
+ #
12
+ # Note: Generated test ids are omitted.
13
+ #
14
+ class Tags
15
+ include Cucumber::Formatter::Io
16
+
17
+ #@api private
18
+ def initialize(runtime, path_or_io, options)
19
+ @io = ensure_io(path_or_io)
20
+ @tags = []
21
+ @scenario_tags = []
22
+ @total_scenarios = 0
23
+ @total_scenarios_without_tags = 0
24
+ @scenarios_without_tags = {}
25
+ @options = options
26
+ end
27
+
28
+ #@api private
29
+ def after_features(features)
30
+ publish_result
31
+ end
32
+
33
+ #@api private
34
+ def before_feature(feature)
35
+ @feature_tags = []
36
+ @in_scenarios = false
37
+ end
38
+
39
+ #@api private
40
+ def tag_name(tag_name)
41
+ if @in_scenarios
42
+ @scenario_tags << tag_name unless tag_name.match(/@test#(\d+)/)
43
+ else
44
+ @feature_tags << tag_name
45
+ end
46
+ end
47
+
48
+ #@api private
49
+ def after_tags(tags)
50
+ @in_scenarios = true unless @in_scenarios
51
+ end
52
+
53
+ #@api private
54
+ def scenario_name(keyword, name, file_colon_line, source_indent)
55
+ scenario_tags = @scenario_tags + @feature_tags
56
+ @tags += scenario_tags
57
+ @total_scenarios += 1
58
+ unless scenario_tags.any?
59
+ @scenarios_without_tags[name] = file_colon_line
60
+ @total_scenarios_without_tags += 1
61
+ end
62
+ @scenario_tags = []
63
+ end
64
+
65
+ private
66
+ def publish_result
67
+ content = {
68
+ untagged: @scenarios_without_tags,
69
+ tags:
70
+ { unique: @tags.uniq.sort,
71
+ total: @tags.size }
72
+ }
73
+ @io.puts(content.to_json({
74
+ indent: ' ',
75
+ space: ' ',
76
+ object_nl: "\n"
77
+ }))
78
+ end
79
+ end
80
+ end
81
+ end