allure-ruby-commons 2.13.1 → 2.14.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,26 +1,43 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "logger"
4
+ require "singleton"
4
5
 
5
6
  module Allure
6
7
  # Allure configuration class
7
8
  class Config
8
- # @return [String] default allure results directory
9
- DEFAULT_RESULTS_DIRECTORY = "reports/allure-results"
10
- # @return [String] default loggin level
11
- DEFAULT_LOGGING_LEVEL = Logger::INFO
12
-
13
- class << self
14
- attr_accessor :link_tms_pattern, :link_issue_pattern, :clean_results_directory
15
- attr_writer :results_directory, :logging_level
16
-
17
- def results_directory
18
- @results_directory || DEFAULT_RESULTS_DIRECTORY
19
- end
20
-
21
- def logging_level
22
- @logging_level || DEFAULT_LOGGING_LEVEL
23
- end
9
+ include Singleton
10
+
11
+ # @return [Array<String>] valid log levels
12
+ LOGLEVELS = %w[DEBUG INFO WARN ERROR FATAL UNKNOWN].freeze
13
+
14
+ attr_writer :environment, :logger
15
+
16
+ attr_accessor :results_directory,
17
+ :logging_level,
18
+ :link_tms_pattern,
19
+ :link_issue_pattern,
20
+ :clean_results_directory
21
+
22
+ def initialize
23
+ @results_directory = "reports/allure-results"
24
+ @logging_level = LOGLEVELS.index(ENV.fetch("ALLURE_LOG_LEVEL", "INFO")) || Logger::INFO
25
+ end
26
+
27
+ # Allure environment
28
+ #
29
+ # @return [String]
30
+ def environment
31
+ return(@environment) if defined?(@environment)
32
+
33
+ @environment ||= ENV["ALLURE_ENVIRONMENT"]
34
+ end
35
+
36
+ # Logger instance
37
+ #
38
+ # @return [Logger]
39
+ def logger
40
+ @logger ||= Logger.new($stdout, level: logging_level)
24
41
  end
25
42
  end
26
43
  end
@@ -9,19 +9,30 @@ module Allure
9
9
  TEST_RESULT_CONTAINER_SUFFIX = "-container.json"
10
10
  # @return [String] attachment file suffix
11
11
  ATTACHMENT_FILE_SUFFIX = "-attachment"
12
+ # @return [String] environment info file
13
+ ENVIRONMENT_FILE = "environment.properties"
14
+ # @return [String] categories definition json
15
+ CATEGORIES_FILE = "categories.json"
16
+
17
+ # File writer instance
18
+ #
19
+ # @param [String] results_directory
20
+ def initialize(results_directory)
21
+ @results_directory = results_directory
22
+ end
12
23
 
13
24
  # Write test result
14
25
  # @param [Allure::TestResult] test_result
15
26
  # @return [void]
16
27
  def write_test_result(test_result)
17
- write("#{test_result.uuid}#{TEST_RESULT_SUFFIX}", test_result.to_json)
28
+ write("#{test_result.uuid}#{TEST_RESULT_SUFFIX}", Oj.dump(test_result))
18
29
  end
19
30
 
20
31
  # Write test result container
21
32
  # @param [Allure::TestResultContainer] test_container_result
22
33
  # @return [void]
23
34
  def write_test_result_container(test_container_result)
24
- write("#{test_container_result.uuid}#{TEST_RESULT_CONTAINER_SUFFIX}", test_container_result.to_json)
35
+ write("#{test_container_result.uuid}#{TEST_RESULT_CONTAINER_SUFFIX}", Oj.dump(test_container_result))
25
36
  end
26
37
 
27
38
  # Write allure attachment file
@@ -32,10 +43,32 @@ module Allure
32
43
  source.is_a?(File) ? copy(source.path, attachment.source) : write(attachment.source, source)
33
44
  end
34
45
 
46
+ # Write allure report environment info
47
+ # @param [Hash<Symbol, String>] environment
48
+ # @return [void]
49
+ def write_environment(environment)
50
+ environment.reduce("") { |e, (k, v)| e + "#{k}=#{v}\n" }.tap do |env|
51
+ write(ENVIRONMENT_FILE, env)
52
+ end
53
+ end
54
+
55
+ # Write categories info
56
+ # @param [File, Array<Allure::Category>] categories
57
+ # @return [void]
58
+ def write_categories(categories)
59
+ if categories.is_a?(File)
60
+ copy(categories.path, CATEGORIES_FILE)
61
+ else
62
+ write(CATEGORIES_FILE, Oj.dump(categories))
63
+ end
64
+ end
65
+
35
66
  private
36
67
 
68
+ attr_reader :results_directory
69
+
37
70
  def output_dir
38
- @output_dir ||= FileUtils.mkpath(Allure::Config.results_directory).first
71
+ @output_dir ||= FileUtils.mkpath(results_directory).first
39
72
  end
40
73
 
41
74
  def write(name, source)
@@ -1,11 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "json"
3
+ require "oj"
4
4
 
5
5
  module Allure
6
6
  # General jsonable object implementation
7
7
  class JSONable
8
- def as_json(_options = {})
8
+ Oj.default_options = { mode: :custom, use_to_hash: true, ascii_only: true }
9
+
10
+ # Return object hash represantation
11
+ # @return [Hash]
12
+ def to_hash
9
13
  instance_variables.each_with_object({}) do |var, map|
10
14
  key = camelcase(var.to_s.delete_prefix("@"))
11
15
  value = instance_variable_get(var)
@@ -13,22 +17,26 @@ module Allure
13
17
  end
14
18
  end
15
19
 
16
- def to_json(*options)
17
- as_json(*options).to_json(*options)
18
- end
19
-
20
+ # Object comparator
21
+ # @param [JSONable] other
22
+ # @return [Booelan]
20
23
  def ==(other)
21
24
  self.class == other.class && state == other.state
22
25
  end
23
26
 
24
27
  protected
25
28
 
29
+ # Object state
30
+ # @return [Array]
26
31
  def state
27
32
  instance_variables.map { |var| instance_variable_get(var) }
28
33
  end
29
34
 
30
35
  private
31
36
 
37
+ # Covert string to camelcase
38
+ # @param [String] str
39
+ # @return [String]
32
40
  def camelcase(str)
33
41
  str = str.gsub(/(?:_+)([a-z])/) { Regexp.last_match(1).upcase }
34
42
  str.gsub(/(\A|\s)([A-Z])/) { Regexp.last_match(1) + Regexp.last_match(2).downcase }
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "jsonable"
4
-
5
3
  module Allure
6
4
  # Allure model attachment object
7
5
  class Attachment < JSONable
@@ -9,6 +7,8 @@ module Allure
9
7
  # @param [String] type attachment type, {Allure::ContentType}
10
8
  # @param [String] source attachment file name
11
9
  def initialize(name:, type:, source:)
10
+ super()
11
+
12
12
  @name = name
13
13
  @type = type
14
14
  @source = source
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Allure
4
+ # Defects category
5
+ class Category < JSONable
6
+ # @param [String] name
7
+ # @param [Array<Allure::Status>] matched_statuses
8
+ # @param [String, Regexp] message_regex
9
+ # @param [String, Regexp] trace_regex
10
+ def initialize(name:, matched_statuses: nil, message_regex: nil, trace_regex: nil)
11
+ super()
12
+
13
+ @name = name
14
+ @matched_statuses = matched_statuses
15
+ @message_regex = message_regex
16
+ @trace_regex = trace_regex
17
+ end
18
+ end
19
+ end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "jsonable"
4
-
5
3
  module Allure
6
4
  # Allure model executable item
7
5
  class ExecutableItem < JSONable
@@ -16,6 +14,8 @@ module Allure
16
14
  # @option options [Array<Allure::Attachment>] :attachments ([])
17
15
  # @option options [Array<Allure::Parameter>] :parameters ([])
18
16
  def initialize(**options)
17
+ super()
18
+
19
19
  @name = options[:name]
20
20
  @description = options[:description]
21
21
  @description_html = options[:description_html]
@@ -27,9 +27,16 @@ module Allure
27
27
  @parameters = options[:parameters] || []
28
28
  end
29
29
 
30
- attr_accessor(
31
- :name, :status, :status_details, :stage, :description, :description_html,
32
- :steps, :attachments, :parameters, :start, :stop
33
- )
30
+ attr_accessor :name,
31
+ :status,
32
+ :status_details,
33
+ :stage,
34
+ :description,
35
+ :description_html,
36
+ :steps,
37
+ :attachments,
38
+ :parameters,
39
+ :start,
40
+ :stop
34
41
  end
35
42
  end
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "jsonable"
4
-
5
3
  module Allure
6
4
  # Allure model label object
7
5
  class Label < JSONable
8
6
  def initialize(name, value)
7
+ super()
8
+
9
9
  @name = name
10
10
  @value = value
11
11
  end
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "jsonable"
4
-
5
3
  module Allure
6
4
  # Allure model link object
7
5
  class Link < JSONable
8
6
  def initialize(type, name, url)
7
+ super()
8
+
9
9
  @type = type
10
10
  @name = name
11
11
  @url = url
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "jsonable"
4
-
5
3
  module Allure
6
4
  # Allure model parameter object
7
5
  class Parameter < JSONable
8
6
  def initialize(name, value)
7
+ super()
8
+
9
9
  @name = name
10
10
  @value = value
11
11
  end
@@ -7,5 +7,6 @@ module Allure
7
7
  BROKEN = :broken
8
8
  PASSED = :passed
9
9
  SKIPPED = :skipped
10
+ UNKNOWN = :unknown
10
11
  end
11
12
  end
@@ -9,6 +9,8 @@ module Allure
9
9
  # @param [String] message
10
10
  # @param [String] trace
11
11
  def initialize(known: false, muted: false, flaky: false, message: nil, trace: nil)
12
+ super()
13
+
12
14
  @known = known
13
15
  @muted = muted
14
16
  @flaky = flaky
@@ -5,6 +5,7 @@ module Allure
5
5
  class TestResult < ExecutableItem
6
6
  # @param [String] uuid
7
7
  # @param [String] history_id
8
+ # @param [String] environment
8
9
  # @param [Hash] options
9
10
  # @option options [String] :name
10
11
  # @option options [String] :full_name
@@ -18,8 +19,10 @@ module Allure
18
19
  # @option options [Array<Allure::Link>] :links ([])
19
20
  # @option options [Array<Allure::Attachment>] :attachments ([])
20
21
  # @option options [Array<Allure::Parameter>] :parameters ([])
21
- def initialize(uuid: UUID.generate, history_id: UUID.generate, **options)
22
+ def initialize(uuid: UUID.generate, history_id: UUID.generate, environment: nil, **options)
22
23
  super
24
+
25
+ @name = test_name(options[:name], environment)
23
26
  @uuid = uuid
24
27
  @history_id = history_id
25
28
  @full_name = options[:full_name] || "Unnamed"
@@ -27,6 +30,23 @@ module Allure
27
30
  @links = options[:links] || []
28
31
  end
29
32
 
30
- attr_accessor :uuid, :history_id, :full_name, :labels, :links
33
+ attr_accessor :uuid,
34
+ :history_id,
35
+ :full_name,
36
+ :labels,
37
+ :links
38
+
39
+ private
40
+
41
+ # Test name prefixed with allure environment
42
+ #
43
+ # @param [String] name
44
+ # @param [String] environment
45
+ # @return [String]
46
+ def test_name(name, environment)
47
+ return name unless environment
48
+
49
+ "#{environment}: #{name}"
50
+ end
31
51
  end
32
52
  end
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "jsonable"
4
3
  module Allure
5
4
  # Allure model step result container
6
5
  class TestResultContainer < JSONable
7
6
  def initialize(uuid: UUID.generate, name: "Unnamed")
7
+ super()
8
+
8
9
  @uuid = uuid
9
10
  @name = name
10
11
  @children = []
@@ -46,14 +46,26 @@ module Allure
46
46
  Label.new(HOST_LABEL_NAME, Socket.gethostname)
47
47
  end
48
48
 
49
+ # Language label
50
+ # @return [Allure::Label]
49
51
  def language_label
50
52
  Label.new(LANGUAGE_LABEL_NAME, "ruby")
51
53
  end
52
54
 
55
+ # Framework label
56
+ # @param [String] value
57
+ # @return [Allure::Label]
53
58
  def framework_label(value)
54
59
  Label.new(FRAMEWORK_LABEL_NAME, value)
55
60
  end
56
61
 
62
+ # Epic label
63
+ # @param [String] value
64
+ # @return [Allure::Label]
65
+ def epic_label(value)
66
+ Label.new(EPIC_LABEL_NAME, value)
67
+ end
68
+
57
69
  # Feature label
58
70
  # @param [String] value
59
71
  # @return [Allure::Label]
@@ -61,6 +73,13 @@ module Allure
61
73
  Label.new(FEATURE_LABEL_NAME, value)
62
74
  end
63
75
 
76
+ # Story label
77
+ # @param [String] value
78
+ # @return [Allure::Label]
79
+ def story_label(value)
80
+ Label.new(STORY_LABEL_NAME, value)
81
+ end
82
+
64
83
  # Package label
65
84
  # @param [String] value
66
85
  # @return [Allure::Label]
@@ -89,13 +108,6 @@ module Allure
89
108
  Label.new(SUB_SUITE_LABEL_NAME, value)
90
109
  end
91
110
 
92
- # Story label
93
- # @param [String] value
94
- # @return [Allure::Label]
95
- def story_label(value)
96
- Label.new(STORY_LABEL_NAME, value)
97
- end
98
-
99
111
  # Test case label
100
112
  # @param [String] value
101
113
  # @return [Allure::Label]
@@ -119,23 +131,25 @@ module Allure
119
131
 
120
132
  # TMS link
121
133
  # @param [String] value
134
+ # @param [String] link_pattern
122
135
  # @return [Allure::Link]
123
- def tms_link(value)
124
- Link.new(TMS_LINK_TYPE, value, tms_url(value))
136
+ def tms_link(value, link_pattern)
137
+ Link.new(TMS_LINK_TYPE, value, url(value, link_pattern))
125
138
  end
126
139
 
127
140
  # Issue link
128
141
  # @param [String] value
142
+ # @param [String] link_pattern
129
143
  # @return [Allure::Link]
130
- def issue_link(value)
131
- Link.new(ISSUE_LINK_TYPE, value, issue_url(value))
144
+ def issue_link(value, link_pattern)
145
+ Link.new(ISSUE_LINK_TYPE, value, url(value, link_pattern))
132
146
  end
133
147
 
134
148
  # Get status based on exception type
135
149
  # @param [Exception] exception
136
150
  # @return [Symbol]
137
151
  def status(exception)
138
- expectation_error?(exception) ? Status::FAILED : Status::BROKEN
152
+ exception.is_a?(RSpec::Expectations::ExpectationNotMetError) ? Status::FAILED : Status::BROKEN
139
153
  end
140
154
 
141
155
  # Get exception status detail
@@ -145,19 +159,20 @@ module Allure
145
159
  StatusDetails.new(message: exception&.message, trace: exception&.backtrace&.join("\n"))
146
160
  end
147
161
 
148
- private
149
-
150
- def tms_url(value)
151
- Allure.configuration.link_tms_pattern.sub("{}", value)
162
+ # Allure attachment object
163
+ # @param [String] name
164
+ # @param [String] type
165
+ # @return [Allure::Attachment]
166
+ def prepare_attachment(name, type)
167
+ extension = ContentType.to_extension(type) || return
168
+ file_name = "#{UUID.generate}-attachment.#{extension}"
169
+ Attachment.new(name: name, source: file_name, type: type)
152
170
  end
153
171
 
154
- def issue_url(value)
155
- Allure.configuration.link_issue_pattern.sub("{}", value)
156
- end
172
+ private
157
173
 
158
- def expectation_error?(exception)
159
- exception.instance_of?(RSpec::Expectations::ExpectationNotMetError) ||
160
- exception.instance_of?(RSpec::Expectations::MultipleExpectationsNotMetError)
174
+ def url(value, link_pattern)
175
+ link_pattern.sub("{}", value)
161
176
  end
162
177
  end
163
178
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Adds support for annotating methods as allure steps
4
+ #
5
+ module AllureStepAnnotation
6
+ # Mark method definition as allure step
7
+ #
8
+ # @param [String] step_name
9
+ # @return [void]
10
+ def step(step_name = "")
11
+ @allure_step = step_name
12
+ end
13
+
14
+ private
15
+
16
+ def singleton_method_added(method_name)
17
+ return super unless @allure_step
18
+
19
+ original_method = singleton_method(method_name)
20
+ step_name = @allure_step.empty? ? method_name.to_s : @allure_step
21
+ @allure_step = nil
22
+
23
+ define_singleton_method(method_name) do |*args, &block|
24
+ Allure.run_step(step_name) { original_method.call(*args, &block) }
25
+ end
26
+ end
27
+
28
+ def method_added(method_name)
29
+ return super unless @allure_step
30
+
31
+ original_method = instance_method(method_name)
32
+ step_name = @allure_step.empty? ? method_name.to_s : @allure_step
33
+ @allure_step = nil
34
+
35
+ define_method(method_name) do |*args, &block|
36
+ Allure.run_step(step_name) { original_method.bind(self).call(*args, &block) }
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Allure
4
+ class TestPlan
5
+ # @return [String] test plan path env var name
6
+ TESTPLAN_PATH = "ALLURE_TESTPLAN_PATH"
7
+
8
+ class << self
9
+ # Allure id's of executable tests
10
+ #
11
+ # @return [Array]
12
+ def test_ids
13
+ @test_ids ||= tests&.map { |test| test[:id] }
14
+ end
15
+
16
+ # Test names of executable tests
17
+ #
18
+ # @return [Array]
19
+ def test_names
20
+ @test_names ||= tests&.map { |test| test[:selector] }
21
+ end
22
+
23
+ private
24
+
25
+ # Tests to execute from allure testplan.json
26
+ #
27
+ # @return [Array<Hash>]
28
+ def tests
29
+ @tests ||= Oj.load_file(ENV[TESTPLAN_PATH], symbol_keys: true)&.fetch(:tests) if ENV[TESTPLAN_PATH]
30
+ rescue Oj::ParseError
31
+ nil
32
+ end
33
+ end
34
+ end
35
+ end