qat-cucumber 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
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