allure-ruby-commons 2.13.1 → 2.14.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.
@@ -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