Class: Fastlane::Actions::CollateJunitReportsAction

Inherits:
Action
  • Object
show all
Defined in:
lib/fastlane/plugin/test_center/actions/collate_junit_reports.rb

Documentation collapse

Class Method Summary collapse

Class Method Details

.attribute_sum_string(node1, node2, attribute) ⇒ Object



162
163
164
165
166
# File 'lib/fastlane/plugin/test_center/actions/collate_junit_reports.rb', line 162

def self.attribute_sum_string(node1, node2, attribute)
  value1 = node1.attribute(attribute).value.to_i
  value2 = node2.attribute(attribute).value.to_i
  (value1 + value2).to_s
end

.authorsObject



236
237
238
# File 'lib/fastlane/plugin/test_center/actions/collate_junit_reports.rb', line 236

def self.authors
  ["lyndsey-ferguson/@lyndseydf"]
end

.available_optionsObject



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/fastlane/plugin/test_center/actions/collate_junit_reports.rb', line 194

def self.available_options
  [
    FastlaneCore::ConfigItem.new(
      key: :reports,
      env_name: 'COLLATE_JUNIT_REPORTS_REPORTS',
      description: 'An array of junit reports to collate. The first report is used as the base into which other reports are merged in',
      optional: false,
      type: Array,
      verify_block: proc do |reports|
        UI.user_error!('No junit report files found') if reports.empty?
        reports.each do |report|
          UI.user_error!("Error: junit report not found: '#{report}'") unless File.exist?(report)
        end
      end
    ),
    FastlaneCore::ConfigItem.new(
      key: :collated_report,
      env_name: 'COLLATE_JUNIT_REPORTS_COLLATED_REPORT',
      description: 'The final junit report file where all testcases will be merged into',
      optional: true,
      default_value: 'result.xml',
      type: String
    )
  ]
end

.categoryObject



240
241
242
# File 'lib/fastlane/plugin/test_center/actions/collate_junit_reports.rb', line 240

def self.category
  :testing
end

.collapse_testcase_multiple_failures_in_testsuite(testsuite) ⇒ Object



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/fastlane/plugin/test_center/actions/collate_junit_reports.rb', line 59

def self.collapse_testcase_multiple_failures_in_testsuite(testsuite)
  testcases_with_failures = REXML::XPath.match(testsuite, 'testcase[failure]')

  while testcases_with_failures.size > 1
    target_testcase = testcases_with_failures.shift

    name = target_testcase.attribute('name').value
    classname = target_testcase.attribute('classname').value

    failures = REXML::XPath.match(testsuite, "testcase[@name='#{name}'][@classname='#{classname}']/failure")
    next unless failures.size > 1

    failures[1..-1].each do |failure|
      failure_clone = failure.clone
      failure_clone.text = failure.text
      target_testcase << failure_clone

      testsuite.delete_element(failure.parent)
      testcases_with_failures.delete(failure.parent)
    end
  end
end

.collate_testsuite(target_testsuite, other_testsuite) ⇒ Object



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/fastlane/plugin/test_center/actions/collate_junit_reports.rb', line 104

def self.collate_testsuite(target_testsuite, other_testsuite)
  other_testsuite.elements.each('testcase') do |testcase|
    classname = testcase.attribute('classname').value
    name = testcase.attribute('name').value
    target_testcase = REXML::XPath.first(target_testsuite, "testcase[@name='#{name}' and @classname='#{classname}']")
    # Replace target_testcase with testcase
    if target_testcase
      verbose("      collate_testsuite with testcase #{name}")
      verbose("      replacing \"#{target_testcase}\" with \"#{testcase}\"")
      parent = target_testcase.parent
      increment_testcase_tries(target_testcase, testcase) unless testcase.root == target_testcase.root 
      parent.insert_after(target_testcase, testcase)
      parent.delete_element(target_testcase)
      verbose("")
      verbose("      target_testcase after replacement \"#{parent}\"")
    else
      target_testsuite << testcase
    end
  end
end

.descriptionObject

:nocov:



178
179
180
# File 'lib/fastlane/plugin/test_center/actions/collate_junit_reports.rb', line 178

def self.description
  "🔷 Combines multiple junit report files into one junit report file"
end

.detailsObject



182
183
184
185
186
187
188
189
190
191
192
# File 'lib/fastlane/plugin/test_center/actions/collate_junit_reports.rb', line 182

def self.details
  "The first junit report is used as the base report. Testcases " \
  "from other reports are added if they do not already exist, or " \
  "if the testcases already exist, they are replaced." \
  "" \
  "This is done because it is assumed that fragile tests, when " \
  "re-run will often succeed due to less interference from other " \
  "tests and the subsequent junit reports will have more passed tests." \
  "" \
  "Inspired by Derek Yang's fastlane-plugin-merge_junit_report"
end

.example_codeObject



220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/fastlane/plugin/test_center/actions/collate_junit_reports.rb', line 220

def self.example_code
  [
    "
    UI.important(
      'example: ' \\
      'collate the xml reports to a temporary file \"result.xml\"'
    )
    reports = Dir['../spec/fixtures/*.xml'].map { |relpath| File.absolute_path(relpath) }
    collate_junit_reports(
      reports: reports.sort_by { |f| File.mtime(f) },
      collated_report: File.join(Dir.mktmpdir, 'result.xml')
    )
    "
  ]
end

.flatten_duplicate_testsuites(report, testsuite) ⇒ Object



82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/fastlane/plugin/test_center/actions/collate_junit_reports.rb', line 82

def self.flatten_duplicate_testsuites(report, testsuite)
  testsuite_name = testsuite.attribute('name').value
  duplicate_testsuites = REXML::XPath.match(report, "//testsuite[@name='#{testsuite_name}']")
  if duplicate_testsuites.size > 1
    verbose("    > flattening_duplicate_testsuites")
    duplicate_testsuites.drop(1).each do |duplicate_testsuite|
      collate_testsuite(testsuite, duplicate_testsuite)
      duplicate_testsuite.parent.delete_element(duplicate_testsuite)
    end
    verbose("    < flattening_duplicate_testsuites")
  end
  update_testsuite_counts(testsuite)
end

.increment_testable_tries(target_testable, other_testable) ⇒ Object



125
126
127
128
129
130
131
# File 'lib/fastlane/plugin/test_center/actions/collate_junit_reports.rb', line 125

def self.increment_testable_tries(target_testable, other_testable)

  try_count = target_testable.attribute('retries')&.value || 1
  other_try_count = other_testable.attribute('retries')&.value || 1

  target_testable.add_attribute('retries', (try_count.to_i + other_try_count.to_i).to_s)
end

.increment_testcase_tries(target_testcase, testcase) ⇒ Object



133
134
135
136
# File 'lib/fastlane/plugin/test_center/actions/collate_junit_reports.rb', line 133

def self.increment_testcase_tries(target_testcase, testcase)
  try_count = target_testcase.attribute('retries')&.value || 0
  testcase.add_attribute('retries', (try_count.to_i + 1).to_s)
end

.is_supported?(platform) ⇒ Boolean

Returns:

  • (Boolean)


244
245
246
# File 'lib/fastlane/plugin/test_center/actions/collate_junit_reports.rb', line 244

def self.is_supported?(platform)
  %i[ios mac].include?(platform)
end

.preprocess_testsuites(report, package, combine_multiple_targets) ⇒ Object



96
97
98
99
100
101
102
# File 'lib/fastlane/plugin/test_center/actions/collate_junit_reports.rb', line 96

def self.preprocess_testsuites(report, package, combine_multiple_targets)
  report.elements.each('//testsuite') do |testsuite|
    flatten_duplicate_testsuites(report, testsuite)
    collapse_testcase_multiple_failures_in_testsuite(testsuite)
    testsuite.add_attribute('package', package) if combine_multiple_targets
  end
end

.run(params) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/fastlane/plugin/test_center/actions/collate_junit_reports.rb', line 6

def self.run(params)
  report_filepaths = params[:reports]
  if report_filepaths.size == 1
    FileUtils.cp(report_filepaths[0], params[:collated_report])
  else
    verbose("collate_junit_reports with #{report_filepaths}")
    reports = report_filepaths.map { |report_filepath| REXML::Document.new(File.new(report_filepath)) }
    packages = reports.map { |r| r.root.attribute('name').value }.uniq
    combine_multiple_targets = packages.size > 1 

    # copy any missing testsuites
    target_report = reports.shift

    package = target_report.root.attribute('name').value
    preprocess_testsuites(target_report, package, combine_multiple_targets)

    reports.each do |report|
      increment_testable_tries(target_report.root, report.root)
      package = report.root.attribute('name').value
      preprocess_testsuites(report, package, combine_multiple_targets)
      verbose("> collating last report file #{report_filepaths.last}")
      report.elements.each('//testsuite') do |testsuite|
        testsuite_name = testsuite.attribute('name').value
        package_attribute = ''
        if combine_multiple_targets
          package_attribute = "@package='#{package}'"
        end
        target_testsuite = REXML::XPath.first(target_report, "//testsuite[@name='#{testsuite_name}' #{package_attribute}]")
        if target_testsuite
          verbose("  > collating testsuite #{testsuite_name}")
          collate_testsuite(target_testsuite, testsuite)
          verbose("  < collating testsuite #{testsuite_name}")
        else
          testable = REXML::XPath.first(target_report, "//testsuites")
          testable << testsuite
        end
      end
      verbose("< collating last report file #{report_filepaths.last}")
    end
    target_report.elements.each('//testsuite') do |testsuite|
      update_testsuite_counts(testsuite)
    end
    testable = REXML::XPath.first(target_report, 'testsuites')
    update_testable_counts(testable)
    testable.add_attribute('name', packages.to_a.join(', '))

    FileUtils.mkdir_p(File.dirname(params[:collated_report]))
    File.open(params[:collated_report], 'w') do |f|
      target_report.write(f, 2)
    end
  end
end

.update_testable_counts(testable) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
# File 'lib/fastlane/plugin/test_center/actions/collate_junit_reports.rb', line 138

def self.update_testable_counts(testable)
  testsuites = REXML::XPath.match(testable, 'testsuite')
  test_count = 0
  failure_count = 0
  testsuites.each do |testsuite|
    test_count += testsuite.attribute('tests').value.to_i
    failure_count += testsuite.attribute('failures').value.to_i
  end
  testable.add_attribute('tests', test_count.to_s)
  testable.add_attribute('failures', failure_count.to_s)
end

.update_testsuite_counts(testsuite) ⇒ Object



150
151
152
153
154
155
156
157
158
159
160
# File 'lib/fastlane/plugin/test_center/actions/collate_junit_reports.rb', line 150

def self.update_testsuite_counts(testsuite)
  testcases = REXML::XPath.match(testsuite, 'testcase')
  testsuite.add_attribute('tests', testcases.size.to_s)
  failure_count = testcases.reduce(0) do |count, testcase|
    if REXML::XPath.first(testcase, 'failure')
      count += 1
    end
    count
  end
  testsuite.add_attribute('failures', failure_count.to_s)
end

.verbose(message) ⇒ Object



168
169
170
171
172
# File 'lib/fastlane/plugin/test_center/actions/collate_junit_reports.rb', line 168

def self.verbose(message)
  return if ENV.fetch('COLLATE_JUNIT_REPORTS_VERBOSITY', 1).to_i.zero?

  UI.verbose(message)
end