From 1e1614d55f054c9b93dd0293bf617319b7c07c64 Mon Sep 17 00:00:00 2001 From: ainame Date: Tue, 20 Jul 2021 11:18:52 +0900 Subject: [PATCH 01/24] Update Gemfile.lock --- Gemfile.lock | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index ea01965..784706f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -6,14 +6,15 @@ PATH GEM remote: https://rubygems.org/ specs: - ast (2.4.0) + ast (2.4.2) diff-lcs (1.3) - jaro_winkler (1.5.3) - parallel (1.17.0) - parser (2.6.4.1) - ast (~> 2.4.0) + parallel (1.20.1) + parser (3.0.2.0) + ast (~> 2.4.1) rainbow (3.0.0) rake (13.0.1) + regexp_parser (2.1.1) + rexml (3.2.5) rspec (3.8.0) rspec-core (~> 3.8.0) rspec-expectations (~> 3.8.0) @@ -27,15 +28,19 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.8.0) rspec-support (3.8.2) - rubocop (0.73.0) - jaro_winkler (~> 1.5.1) + rubocop (1.18.3) parallel (~> 1.10) - parser (>= 2.6) + parser (>= 3.0.0.0) rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8, < 3.0) + rexml + rubocop-ast (>= 1.7.0, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 1.7) - ruby-progressbar (1.10.1) - unicode-display_width (1.6.0) + unicode-display_width (>= 1.4.0, < 3.0) + rubocop-ast (1.8.0) + parser (>= 3.0.1.1) + ruby-progressbar (1.11.0) + unicode-display_width (2.0.0) PLATFORMS ruby @@ -48,4 +53,4 @@ DEPENDENCIES xcresult! BUNDLED WITH - 2.0.2 + 2.2.17 From 49f71a1d3dbcf608aadab2c4538e27176c766855 Mon Sep 17 00:00:00 2001 From: ainame Date: Wed, 21 Jul 2021 05:58:08 +0900 Subject: [PATCH 02/24] Add XCResult::ModelGenerator --- lib/xcresult/model_generator.rb | 179 ++++++++++++++++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 lib/xcresult/model_generator.rb diff --git a/lib/xcresult/model_generator.rb b/lib/xcresult/model_generator.rb new file mode 100644 index 0000000..369f139 --- /dev/null +++ b/lib/xcresult/model_generator.rb @@ -0,0 +1,179 @@ +require 'json' +require 'erb' + +module XCResult + class ModelGenerator + def self.format_description + `xcrun xcresulttool formatDescription get` + end + + def self.generate(destination) + parser = Parser.new(source: format_description) + parser.parse + generator = Generator.new(parser.source) + generator.write(destination) + end + + class Generator + def initialize(source) + @source = source + @type_to_kind = source.types.map { |t| [t.name, t.kind] }.to_h + end + + def write(destination) + file = File.open(destination, 'w+') + file.puts(<<~"HEADER") + # This is a generated file. Don't modify this directly! + # Last generated at: #{Time.now.utc} + # + # #{@source.name} + # #{@source.version} + # #{@source.signature} + require 'time' + + HEADER + + file.puts(<<~TEMPLATE) + module XCResult + module Models + + TEMPLATE + + @source.types.each do |type| + type_text = compose_type(type, 2 * 2) + file.puts(type_text) + file.puts('') + end + + file.puts(<<~TEMPLATE) + end + end + TEMPLATE + ensure + file.close + end + + def compose_type(type, indentation) + type_def = <<~"TYPE" + <%= type.raw_text.each_line.map { |line| '# ' + line[2..-1] }.join %> + <% if type.supertype %> + class <%= type.name %> < <%= type.supertype %> + <% else %> + class <%= type.name %> + <% end%> + <% type.properties.each do |property| %> + <%= property.rdoc_comment %> + attr_reader :<%= property.name_in_snake_case %> + <% end %> + + def initialize(data) + <% type.properties.each do |property| %> + @<%= property.name_in_snake_case %> = <%= property.mapping(type_to_kind[property.main_type], 'data') %> + <% end %> + end + end + TYPE + type_def = ERB.new(type_def, trim_mode: '<>').result_with_hash(type: type, type_to_kind: @type_to_kind) + type_def.each_line.map { |line| "#{' ' * indentation}#{line}" }.join + end + end + + class Parser + attr_reader :source + + def initialize(source: ) + @raw_source = source + @lines = source.each_line.to_a + @source = Source.new + end + + def parse + @source.name = @lines.shift.chomp + @source.version = @lines.shift.chomp + @source.signature = @lines.shift.chomp + + # Drop "- Types" lines + @lines.shift + + # Parsing Types + types = [] + type = nil + + until @lines.empty? + line = @lines.shift + case line + when /\s*-\s+(.*)$/ + types << type if type + type = Type.new(name: Regexp.last_match(1), raw_text: line, properties: []) + when /\s*\*\s*Supertype: (.*)$/ + type.supertype = Regexp.last_match(1) + type.raw_text << line + when /\s*\*\s*Kind: (.*)$/ + type.kind = Regexp.last_match(1) + type.raw_text << line + when /\s*\*\s*Properties:$/ + type.raw_text << line + parse_properties(type) + end + end + + types << type if type + @source.types = types + end + + def parse_properties(type) + while @lines.first =~ /\s*\+\s(?.*):\s*(?.*)$/ + optional = false + array = false + parsed_name = Regexp.last_match[:name] + parsed_type = Regexp.last_match[:type] + main_type = parsed_type + + if parsed_type =~ /^\[(.*)\]$/ + array = true + main_type = Regexp.last_match[1] + elsif parsed_type =~ /^(.*)\?$/ + optional = true + main_type = Regexp.last_match[1] + end + type.properties << Property.new(name: parsed_name, type: parsed_type, main_type: main_type, optional: optional, array: array) + type.raw_text << @lines.shift + end + end + end + + Source = Struct.new(:name, :version, :signature, :types, keyword_init: true) + Type = Struct.new(:name, :supertype, :kind, :properties, :raw_text, keyword_init: true) + Property = Struct.new(:name, :type, :main_type, :optional, :array, keyword_init: true) do + def name_in_snake_case + name.gsub(/([a-z])(?=[A-Z])/) { (Regexp.last_match[1] || Regexp.last_match[2]) << '_' }.downcase + end + + def mapping(kind, variable_name) + return "#{variable_name}.fetch('#{name}', {})['_values'].map {|d| #{_mapping(kind, 'd')} }" if array + + _mapping(kind, variable_name) + end + + def _mapping(kind, variable_name) + if kind == 'object' + "Kernel.const_get(\"XCResult::Models::\#{#{variable_name}['_type']['_name']}\").new(#{variable_name}.fetch('#{name}')['_value'])" + elsif kind == 'value' && main_type == 'Date' + "Time.parse(#{variable_name}.fetch('#{name}')['_value'])" + elsif kind == 'value' + "#{variable_name}.fetch('#{name}')['_value']" + end + end + + def rdoc_comment + if array + "# @return [Array<#{main_type}>] #{name}" + elsif optional + "# @return [#{main_type}, nil] #{name}" + else + "# @return [#{main_type}] #{name}" + end + end + end + end +end From 62ecc34aaf62fdd940003a7bb3b80550b4b5b23b Mon Sep 17 00:00:00 2001 From: ainame Date: Wed, 21 Jul 2021 06:08:02 +0900 Subject: [PATCH 03/24] Add a script to generete models --- bin/generate_models | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100755 bin/generate_models diff --git a/bin/generate_models b/bin/generate_models new file mode 100755 index 0000000..eb2d65a --- /dev/null +++ b/bin/generate_models @@ -0,0 +1,6 @@ +#! /usr/bin/env ruby + +require_relative '../lib/xcresult/model_generator' + +XCResult::ModelGenerator.generate(File.expand_path(File.join(File.dirname(__FILE__), '../lib/xcresult/models.gen.rb'))) +puts 'Done! Check out lib/xcresult/models.gen.rb' From 9d287c0acba9ab88cdc54bedb113de2a27f64429 Mon Sep 17 00:00:00 2001 From: ainame Date: Wed, 21 Jul 2021 06:16:37 +0900 Subject: [PATCH 04/24] Add nil check and Double type support --- lib/xcresult/model_generator.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/xcresult/model_generator.rb b/lib/xcresult/model_generator.rb index 369f139..b2ee11e 100644 --- a/lib/xcresult/model_generator.rb +++ b/lib/xcresult/model_generator.rb @@ -152,7 +152,7 @@ def name_in_snake_case def mapping(kind, variable_name) return "#{variable_name}.fetch('#{name}', {})['_values'].map {|d| #{_mapping(kind, 'd')} }" if array - _mapping(kind, variable_name) + _mapping(kind, variable_name) + (optional ? " if #{variable_name}[#{name}]" : '') end def _mapping(kind, variable_name) @@ -160,7 +160,11 @@ def _mapping(kind, variable_name) "Kernel.const_get(\"XCResult::Models::\#{#{variable_name}['_type']['_name']}\").new(#{variable_name}.fetch('#{name}')['_value'])" elsif kind == 'value' && main_type == 'Date' "Time.parse(#{variable_name}.fetch('#{name}')['_value'])" - elsif kind == 'value' + elsif kind == 'value' && main_type == 'Int' + "#{variable_name}.fetch('#{name}')['_value'].to_i" + elsif kind == 'value' && main_type == 'Double' + "#{variable_name}.fetch('#{name}')['_value'].to_f" + else "#{variable_name}.fetch('#{name}')['_value']" end end From ff218c245ceb76d80e47e7ed9bb371613fc48060 Mon Sep 17 00:00:00 2001 From: ainame Date: Wed, 21 Jul 2021 06:42:07 +0900 Subject: [PATCH 05/24] Use TSort to define classes that depend on another class --- lib/xcresult/model_generator.rb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/xcresult/model_generator.rb b/lib/xcresult/model_generator.rb index b2ee11e..b697154 100644 --- a/lib/xcresult/model_generator.rb +++ b/lib/xcresult/model_generator.rb @@ -1,5 +1,6 @@ require 'json' require 'erb' +require 'tsort' module XCResult class ModelGenerator @@ -39,7 +40,7 @@ module Models TEMPLATE - @source.types.each do |type| + sorted_types.each do |type| type_text = compose_type(type, 2 * 2) file.puts(type_text) file.puts('') @@ -53,6 +54,14 @@ module Models file.close end + # Use topological sort to correctly defines classes that may depend on another as its super class + def sorted_types + h = @source.types.map {|x| [x, [@source.types.find {|y| y.name == x.supertype}].compact] }.to_h + each_node = ->(&b) { h.each_key(&b) } + each_child = ->(n, &b) { h[n].each(&b) } + TSort.tsort(each_node, each_child) + end + def compose_type(type, indentation) type_def = <<~"TYPE" <%= type.raw_text.each_line.map { |line| '# ' + line[2..-1] }.join %> From 5f870ff5c36895626f312da5dd2492266943e324 Mon Sep 17 00:00:00 2001 From: ainame Date: Wed, 21 Jul 2021 07:19:20 +0900 Subject: [PATCH 06/24] Fix bugs --- lib/xcresult/model_generator.rb | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/xcresult/model_generator.rb b/lib/xcresult/model_generator.rb index b697154..a2e01d8 100644 --- a/lib/xcresult/model_generator.rb +++ b/lib/xcresult/model_generator.rb @@ -159,22 +159,24 @@ def name_in_snake_case end def mapping(kind, variable_name) - return "#{variable_name}.fetch('#{name}', {})['_values'].map {|d| #{_mapping(kind, 'd')} }" if array + return "(#{variable_name}.dig('#{name}', '_values') || []).map {|d| #{_mapping(kind, 'd')} }" if array - _mapping(kind, variable_name) + (optional ? " if #{variable_name}[#{name}]" : '') + _mapping(kind, variable_name) + (optional ? " if #{variable_name}['#{name}']" : '') end def _mapping(kind, variable_name) if kind == 'object' - "Kernel.const_get(\"XCResult::Models::\#{#{variable_name}['_type']['_name']}\").new(#{variable_name}.fetch('#{name}')['_value'])" + type_access_key = array ? "'_type', '_name'" : "'#{name}', '_type', '_name'" + value_access_key = array ? variable_name : "#{variable_name}.dig('#{name}')" + "Kernel.const_get(\"XCResult::Models::\#{#{variable_name}.dig(#{type_access_key})}\").new(#{value_access_key})" elsif kind == 'value' && main_type == 'Date' - "Time.parse(#{variable_name}.fetch('#{name}')['_value'])" + "Time.parse(#{variable_name}.dig('#{name}', '_value'))" elsif kind == 'value' && main_type == 'Int' - "#{variable_name}.fetch('#{name}')['_value'].to_i" + "#{variable_name}.dig('#{name}', '_value').to_i" elsif kind == 'value' && main_type == 'Double' - "#{variable_name}.fetch('#{name}')['_value'].to_f" + "#{variable_name}.dig('#{name}', '_value').to_f" else - "#{variable_name}.fetch('#{name}')['_value']" + "#{variable_name}.dig('#{name}', '_value')" end end From 3eae67ca14c474c70a311718eb0355a775e108c9 Mon Sep 17 00:00:00 2001 From: ainame Date: Wed, 21 Jul 2021 07:19:31 +0900 Subject: [PATCH 07/24] Migrate existing models --- lib/xcresult/models.gen.rb | 1449 ++++++++++++++++++++++++++++++++++++ lib/xcresult/models.rb | 573 ++------------ lib/xcresult/parser.rb | 4 +- 3 files changed, 1498 insertions(+), 528 deletions(-) create mode 100644 lib/xcresult/models.gen.rb diff --git a/lib/xcresult/models.gen.rb b/lib/xcresult/models.gen.rb new file mode 100644 index 0000000..9738aba --- /dev/null +++ b/lib/xcresult/models.gen.rb @@ -0,0 +1,1449 @@ +# This is a generated file. Don't modify this directly! +# Last generated at: 2021-07-20 22:18:47 UTC +# +# Name: Xcode Result Types +# Version: 3.30 +# Signature: Vu9Uty9iL1U= +require 'time' + +module XCResult + module Models + + # - ActionAbstractTestSummary + # * Kind: object + # * Properties: + # + name: String? + class ActionAbstractTestSummary + # @return [String, nil] name + attr_reader :name + + def initialize(data) + @name = data.dig('name', '_value') if data['name'] + end + end + + # - ActionDeviceRecord + # * Kind: object + # * Properties: + # + name: String + # + isConcreteDevice: Bool + # + operatingSystemVersion: String + # + operatingSystemVersionWithBuildNumber: String + # + nativeArchitecture: String + # + modelName: String + # + modelCode: String + # + modelUTI: String + # + identifier: String + # + isWireless: Bool + # + cpuKind: String + # + cpuCount: Int? + # + cpuSpeedInMHz: Int? + # + busSpeedInMHz: Int? + # + ramSizeInMegabytes: Int? + # + physicalCPUCoresPerPackage: Int? + # + logicalCPUCoresPerPackage: Int? + # + platformRecord: ActionPlatformRecord + class ActionDeviceRecord + # @return [String] name + attr_reader :name + # @return [Bool] isConcreteDevice + attr_reader :is_concrete_device + # @return [String] operatingSystemVersion + attr_reader :operating_system_version + # @return [String] operatingSystemVersionWithBuildNumber + attr_reader :operating_system_version_with_build_number + # @return [String] nativeArchitecture + attr_reader :native_architecture + # @return [String] modelName + attr_reader :model_name + # @return [String] modelCode + attr_reader :model_code + # @return [String] modelUTI + attr_reader :model_uti + # @return [String] identifier + attr_reader :identifier + # @return [Bool] isWireless + attr_reader :is_wireless + # @return [String] cpuKind + attr_reader :cpu_kind + # @return [Int, nil] cpuCount + attr_reader :cpu_count + # @return [Int, nil] cpuSpeedInMHz + attr_reader :cpu_speed_in_mhz + # @return [Int, nil] busSpeedInMHz + attr_reader :bus_speed_in_mhz + # @return [Int, nil] ramSizeInMegabytes + attr_reader :ram_size_in_megabytes + # @return [Int, nil] physicalCPUCoresPerPackage + attr_reader :physical_cpucores_per_package + # @return [Int, nil] logicalCPUCoresPerPackage + attr_reader :logical_cpucores_per_package + # @return [ActionPlatformRecord] platformRecord + attr_reader :platform_record + + def initialize(data) + @name = data.dig('name', '_value') + @is_concrete_device = data.dig('isConcreteDevice', '_value') + @operating_system_version = data.dig('operatingSystemVersion', '_value') + @operating_system_version_with_build_number = data.dig('operatingSystemVersionWithBuildNumber', '_value') + @native_architecture = data.dig('nativeArchitecture', '_value') + @model_name = data.dig('modelName', '_value') + @model_code = data.dig('modelCode', '_value') + @model_uti = data.dig('modelUTI', '_value') + @identifier = data.dig('identifier', '_value') + @is_wireless = data.dig('isWireless', '_value') + @cpu_kind = data.dig('cpuKind', '_value') + @cpu_count = data.dig('cpuCount', '_value').to_i if data['cpuCount'] + @cpu_speed_in_mhz = data.dig('cpuSpeedInMHz', '_value').to_i if data['cpuSpeedInMHz'] + @bus_speed_in_mhz = data.dig('busSpeedInMHz', '_value').to_i if data['busSpeedInMHz'] + @ram_size_in_megabytes = data.dig('ramSizeInMegabytes', '_value').to_i if data['ramSizeInMegabytes'] + @physical_cpucores_per_package = data.dig('physicalCPUCoresPerPackage', '_value').to_i if data['physicalCPUCoresPerPackage'] + @logical_cpucores_per_package = data.dig('logicalCPUCoresPerPackage', '_value').to_i if data['logicalCPUCoresPerPackage'] + @platform_record = Kernel.const_get("XCResult::Models::#{data.dig('platformRecord', '_type', '_name')}").new(data.dig('platformRecord')) + end + end + + # - ActionPlatformRecord + # * Kind: object + # * Properties: + # + identifier: String + # + userDescription: String + class ActionPlatformRecord + # @return [String] identifier + attr_reader :identifier + # @return [String] userDescription + attr_reader :user_description + + def initialize(data) + @identifier = data.dig('identifier', '_value') + @user_description = data.dig('userDescription', '_value') + end + end + + # - ActionRecord + # * Kind: object + # * Properties: + # + schemeCommandName: String + # + schemeTaskName: String + # + title: String? + # + startedTime: Date + # + endedTime: Date + # + runDestination: ActionRunDestinationRecord + # + buildResult: ActionResult + # + actionResult: ActionResult + class ActionRecord + # @return [String] schemeCommandName + attr_reader :scheme_command_name + # @return [String] schemeTaskName + attr_reader :scheme_task_name + # @return [String, nil] title + attr_reader :title + # @return [Date] startedTime + attr_reader :started_time + # @return [Date] endedTime + attr_reader :ended_time + # @return [ActionRunDestinationRecord] runDestination + attr_reader :run_destination + # @return [ActionResult] buildResult + attr_reader :build_result + # @return [ActionResult] actionResult + attr_reader :action_result + + def initialize(data) + @scheme_command_name = data.dig('schemeCommandName', '_value') + @scheme_task_name = data.dig('schemeTaskName', '_value') + @title = data.dig('title', '_value') if data['title'] + @started_time = Time.parse(data.dig('startedTime', '_value')) + @ended_time = Time.parse(data.dig('endedTime', '_value')) + @run_destination = Kernel.const_get("XCResult::Models::#{data.dig('runDestination', '_type', '_name')}").new(data.dig('runDestination')) + @build_result = Kernel.const_get("XCResult::Models::#{data.dig('buildResult', '_type', '_name')}").new(data.dig('buildResult')) + @action_result = Kernel.const_get("XCResult::Models::#{data.dig('actionResult', '_type', '_name')}").new(data.dig('actionResult')) + end + end + + # - ActionResult + # * Kind: object + # * Properties: + # + resultName: String + # + status: String + # + metrics: ResultMetrics + # + issues: ResultIssueSummaries + # + coverage: CodeCoverageInfo + # + timelineRef: Reference? + # + logRef: Reference? + # + testsRef: Reference? + # + diagnosticsRef: Reference? + class ActionResult + # @return [String] resultName + attr_reader :result_name + # @return [String] status + attr_reader :status + # @return [ResultMetrics] metrics + attr_reader :metrics + # @return [ResultIssueSummaries] issues + attr_reader :issues + # @return [CodeCoverageInfo] coverage + attr_reader :coverage + # @return [Reference, nil] timelineRef + attr_reader :timeline_ref + # @return [Reference, nil] logRef + attr_reader :log_ref + # @return [Reference, nil] testsRef + attr_reader :tests_ref + # @return [Reference, nil] diagnosticsRef + attr_reader :diagnostics_ref + + def initialize(data) + @result_name = data.dig('resultName', '_value') + @status = data.dig('status', '_value') + @metrics = Kernel.const_get("XCResult::Models::#{data.dig('metrics', '_type', '_name')}").new(data.dig('metrics')) + @issues = Kernel.const_get("XCResult::Models::#{data.dig('issues', '_type', '_name')}").new(data.dig('issues')) + @coverage = Kernel.const_get("XCResult::Models::#{data.dig('coverage', '_type', '_name')}").new(data.dig('coverage')) + @timeline_ref = Kernel.const_get("XCResult::Models::#{data.dig('timelineRef', '_type', '_name')}").new(data.dig('timelineRef')) if data['timelineRef'] + @log_ref = Kernel.const_get("XCResult::Models::#{data.dig('logRef', '_type', '_name')}").new(data.dig('logRef')) if data['logRef'] + @tests_ref = Kernel.const_get("XCResult::Models::#{data.dig('testsRef', '_type', '_name')}").new(data.dig('testsRef')) if data['testsRef'] + @diagnostics_ref = Kernel.const_get("XCResult::Models::#{data.dig('diagnosticsRef', '_type', '_name')}").new(data.dig('diagnosticsRef')) if data['diagnosticsRef'] + end + end + + # - ActionRunDestinationRecord + # * Kind: object + # * Properties: + # + displayName: String + # + targetArchitecture: String + # + targetDeviceRecord: ActionDeviceRecord + # + localComputerRecord: ActionDeviceRecord + # + targetSDKRecord: ActionSDKRecord + class ActionRunDestinationRecord + # @return [String] displayName + attr_reader :display_name + # @return [String] targetArchitecture + attr_reader :target_architecture + # @return [ActionDeviceRecord] targetDeviceRecord + attr_reader :target_device_record + # @return [ActionDeviceRecord] localComputerRecord + attr_reader :local_computer_record + # @return [ActionSDKRecord] targetSDKRecord + attr_reader :target_sdkrecord + + def initialize(data) + @display_name = data.dig('displayName', '_value') + @target_architecture = data.dig('targetArchitecture', '_value') + @target_device_record = Kernel.const_get("XCResult::Models::#{data.dig('targetDeviceRecord', '_type', '_name')}").new(data.dig('targetDeviceRecord')) + @local_computer_record = Kernel.const_get("XCResult::Models::#{data.dig('localComputerRecord', '_type', '_name')}").new(data.dig('localComputerRecord')) + @target_sdkrecord = Kernel.const_get("XCResult::Models::#{data.dig('targetSDKRecord', '_type', '_name')}").new(data.dig('targetSDKRecord')) + end + end + + # - ActionSDKRecord + # * Kind: object + # * Properties: + # + name: String + # + identifier: String + # + operatingSystemVersion: String + # + isInternal: Bool + class ActionSDKRecord + # @return [String] name + attr_reader :name + # @return [String] identifier + attr_reader :identifier + # @return [String] operatingSystemVersion + attr_reader :operating_system_version + # @return [Bool] isInternal + attr_reader :is_internal + + def initialize(data) + @name = data.dig('name', '_value') + @identifier = data.dig('identifier', '_value') + @operating_system_version = data.dig('operatingSystemVersion', '_value') + @is_internal = data.dig('isInternal', '_value') + end + end + + # - ActionTestActivitySummary + # * Kind: object + # * Properties: + # + title: String + # + activityType: String + # + uuid: String + # + start: Date? + # + finish: Date? + # + attachments: [ActionTestAttachment] + # + subactivities: [ActionTestActivitySummary] + # + failureSummaryIDs: [String] + # + expectedFailureIDs: [String] + class ActionTestActivitySummary + # @return [String] title + attr_reader :title + # @return [String] activityType + attr_reader :activity_type + # @return [String] uuid + attr_reader :uuid + # @return [Date, nil] start + attr_reader :start + # @return [Date, nil] finish + attr_reader :finish + # @return [Array] attachments + attr_reader :attachments + # @return [Array] subactivities + attr_reader :subactivities + # @return [Array] failureSummaryIDs + attr_reader :failure_summary_ids + # @return [Array] expectedFailureIDs + attr_reader :expected_failure_ids + + def initialize(data) + @title = data.dig('title', '_value') + @activity_type = data.dig('activityType', '_value') + @uuid = data.dig('uuid', '_value') + @start = Time.parse(data.dig('start', '_value')) if data['start'] + @finish = Time.parse(data.dig('finish', '_value')) if data['finish'] + @attachments = (data.dig('attachments', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + @subactivities = (data.dig('subactivities', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + @failure_summary_ids = (data.dig('failureSummaryIDs', '_values') || []).map {|d| d.dig('failureSummaryIDs', '_value') } + @expected_failure_ids = (data.dig('expectedFailureIDs', '_values') || []).map {|d| d.dig('expectedFailureIDs', '_value') } + end + end + + # - ActionTestAttachment + # * Kind: object + # * Properties: + # + uniformTypeIdentifier: String + # + name: String? + # + timestamp: Date? + # + userInfo: SortedKeyValueArray? + # + lifetime: String + # + inActivityIdentifier: Int + # + filename: String? + # + payloadRef: Reference? + # + payloadSize: Int + class ActionTestAttachment + # @return [String] uniformTypeIdentifier + attr_reader :uniform_type_identifier + # @return [String, nil] name + attr_reader :name + # @return [Date, nil] timestamp + attr_reader :timestamp + # @return [SortedKeyValueArray, nil] userInfo + attr_reader :user_info + # @return [String] lifetime + attr_reader :lifetime + # @return [Int] inActivityIdentifier + attr_reader :in_activity_identifier + # @return [String, nil] filename + attr_reader :filename + # @return [Reference, nil] payloadRef + attr_reader :payload_ref + # @return [Int] payloadSize + attr_reader :payload_size + + def initialize(data) + @uniform_type_identifier = data.dig('uniformTypeIdentifier', '_value') + @name = data.dig('name', '_value') if data['name'] + @timestamp = Time.parse(data.dig('timestamp', '_value')) if data['timestamp'] + @user_info = Kernel.const_get("XCResult::Models::#{data.dig('userInfo', '_type', '_name')}").new(data.dig('userInfo')) if data['userInfo'] + @lifetime = data.dig('lifetime', '_value') + @in_activity_identifier = data.dig('inActivityIdentifier', '_value').to_i + @filename = data.dig('filename', '_value') if data['filename'] + @payload_ref = Kernel.const_get("XCResult::Models::#{data.dig('payloadRef', '_type', '_name')}").new(data.dig('payloadRef')) if data['payloadRef'] + @payload_size = data.dig('payloadSize', '_value').to_i + end + end + + # - ActionTestExpectedFailure + # * Kind: object + # * Properties: + # + uuid: String + # + failureReason: String? + # + failureSummary: ActionTestFailureSummary? + # + isTopLevelFailure: Bool + class ActionTestExpectedFailure + # @return [String] uuid + attr_reader :uuid + # @return [String, nil] failureReason + attr_reader :failure_reason + # @return [ActionTestFailureSummary, nil] failureSummary + attr_reader :failure_summary + # @return [Bool] isTopLevelFailure + attr_reader :is_top_level_failure + + def initialize(data) + @uuid = data.dig('uuid', '_value') + @failure_reason = data.dig('failureReason', '_value') if data['failureReason'] + @failure_summary = Kernel.const_get("XCResult::Models::#{data.dig('failureSummary', '_type', '_name')}").new(data.dig('failureSummary')) if data['failureSummary'] + @is_top_level_failure = data.dig('isTopLevelFailure', '_value') + end + end + + # - ActionTestFailureSummary + # * Kind: object + # * Properties: + # + message: String? + # + fileName: String + # + lineNumber: Int + # + isPerformanceFailure: Bool + # + uuid: String + # + issueType: String? + # + detailedDescription: String? + # + attachments: [ActionTestAttachment] + # + associatedError: TestAssociatedError? + # + sourceCodeContext: SourceCodeContext? + # + timestamp: Date? + # + isTopLevelFailure: Bool + class ActionTestFailureSummary + # @return [String, nil] message + attr_reader :message + # @return [String] fileName + attr_reader :file_name + # @return [Int] lineNumber + attr_reader :line_number + # @return [Bool] isPerformanceFailure + attr_reader :is_performance_failure + # @return [String] uuid + attr_reader :uuid + # @return [String, nil] issueType + attr_reader :issue_type + # @return [String, nil] detailedDescription + attr_reader :detailed_description + # @return [Array] attachments + attr_reader :attachments + # @return [TestAssociatedError, nil] associatedError + attr_reader :associated_error + # @return [SourceCodeContext, nil] sourceCodeContext + attr_reader :source_code_context + # @return [Date, nil] timestamp + attr_reader :timestamp + # @return [Bool] isTopLevelFailure + attr_reader :is_top_level_failure + + def initialize(data) + @message = data.dig('message', '_value') if data['message'] + @file_name = data.dig('fileName', '_value') + @line_number = data.dig('lineNumber', '_value').to_i + @is_performance_failure = data.dig('isPerformanceFailure', '_value') + @uuid = data.dig('uuid', '_value') + @issue_type = data.dig('issueType', '_value') if data['issueType'] + @detailed_description = data.dig('detailedDescription', '_value') if data['detailedDescription'] + @attachments = (data.dig('attachments', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + @associated_error = Kernel.const_get("XCResult::Models::#{data.dig('associatedError', '_type', '_name')}").new(data.dig('associatedError')) if data['associatedError'] + @source_code_context = Kernel.const_get("XCResult::Models::#{data.dig('sourceCodeContext', '_type', '_name')}").new(data.dig('sourceCodeContext')) if data['sourceCodeContext'] + @timestamp = Time.parse(data.dig('timestamp', '_value')) if data['timestamp'] + @is_top_level_failure = data.dig('isTopLevelFailure', '_value') + end + end + + # - ActionTestSummaryIdentifiableObject + # * Supertype: ActionAbstractTestSummary + # * Kind: object + # * Properties: + # + identifier: String? + class ActionTestSummaryIdentifiableObject < ActionAbstractTestSummary + # @return [String, nil] identifier + attr_reader :identifier + + def initialize(data) + @identifier = data.dig('identifier', '_value') if data['identifier'] + end + end + + # - ActionTestMetadata + # * Supertype: ActionTestSummaryIdentifiableObject + # * Kind: object + # * Properties: + # + testStatus: String + # + duration: Double? + # + summaryRef: Reference? + # + performanceMetricsCount: Int + # + failureSummariesCount: Int + # + activitySummariesCount: Int + class ActionTestMetadata < ActionTestSummaryIdentifiableObject + # @return [String] testStatus + attr_reader :test_status + # @return [Double, nil] duration + attr_reader :duration + # @return [Reference, nil] summaryRef + attr_reader :summary_ref + # @return [Int] performanceMetricsCount + attr_reader :performance_metrics_count + # @return [Int] failureSummariesCount + attr_reader :failure_summaries_count + # @return [Int] activitySummariesCount + attr_reader :activity_summaries_count + + def initialize(data) + @test_status = data.dig('testStatus', '_value') + @duration = data.dig('duration', '_value').to_f if data['duration'] + @summary_ref = Kernel.const_get("XCResult::Models::#{data.dig('summaryRef', '_type', '_name')}").new(data.dig('summaryRef')) if data['summaryRef'] + @performance_metrics_count = data.dig('performanceMetricsCount', '_value').to_i + @failure_summaries_count = data.dig('failureSummariesCount', '_value').to_i + @activity_summaries_count = data.dig('activitySummariesCount', '_value').to_i + end + end + + # - ActionTestNoticeSummary + # * Kind: object + # * Properties: + # + message: String? + # + fileName: String + # + lineNumber: Int + class ActionTestNoticeSummary + # @return [String, nil] message + attr_reader :message + # @return [String] fileName + attr_reader :file_name + # @return [Int] lineNumber + attr_reader :line_number + + def initialize(data) + @message = data.dig('message', '_value') if data['message'] + @file_name = data.dig('fileName', '_value') + @line_number = data.dig('lineNumber', '_value').to_i + end + end + + # - ActionTestPerformanceMetricSummary + # * Kind: object + # * Properties: + # + displayName: String + # + unitOfMeasurement: String + # + measurements: [Double] + # + identifier: String? + # + baselineName: String? + # + baselineAverage: Double? + # + maxPercentRegression: Double? + # + maxPercentRelativeStandardDeviation: Double? + # + maxRegression: Double? + # + maxStandardDeviation: Double? + # + polarity: String? + class ActionTestPerformanceMetricSummary + # @return [String] displayName + attr_reader :display_name + # @return [String] unitOfMeasurement + attr_reader :unit_of_measurement + # @return [Array] measurements + attr_reader :measurements + # @return [String, nil] identifier + attr_reader :identifier + # @return [String, nil] baselineName + attr_reader :baseline_name + # @return [Double, nil] baselineAverage + attr_reader :baseline_average + # @return [Double, nil] maxPercentRegression + attr_reader :max_percent_regression + # @return [Double, nil] maxPercentRelativeStandardDeviation + attr_reader :max_percent_relative_standard_deviation + # @return [Double, nil] maxRegression + attr_reader :max_regression + # @return [Double, nil] maxStandardDeviation + attr_reader :max_standard_deviation + # @return [String, nil] polarity + attr_reader :polarity + + def initialize(data) + @display_name = data.dig('displayName', '_value') + @unit_of_measurement = data.dig('unitOfMeasurement', '_value') + @measurements = (data.dig('measurements', '_values') || []).map {|d| d.dig('measurements', '_value').to_f } + @identifier = data.dig('identifier', '_value') if data['identifier'] + @baseline_name = data.dig('baselineName', '_value') if data['baselineName'] + @baseline_average = data.dig('baselineAverage', '_value').to_f if data['baselineAverage'] + @max_percent_regression = data.dig('maxPercentRegression', '_value').to_f if data['maxPercentRegression'] + @max_percent_relative_standard_deviation = data.dig('maxPercentRelativeStandardDeviation', '_value').to_f if data['maxPercentRelativeStandardDeviation'] + @max_regression = data.dig('maxRegression', '_value').to_f if data['maxRegression'] + @max_standard_deviation = data.dig('maxStandardDeviation', '_value').to_f if data['maxStandardDeviation'] + @polarity = data.dig('polarity', '_value') if data['polarity'] + end + end + + # - ActionTestPlanRunSummaries + # * Kind: object + # * Properties: + # + summaries: [ActionTestPlanRunSummary] + class ActionTestPlanRunSummaries + # @return [Array] summaries + attr_reader :summaries + + def initialize(data) + @summaries = (data.dig('summaries', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + end + end + + # - ActionTestPlanRunSummary + # * Supertype: ActionAbstractTestSummary + # * Kind: object + # * Properties: + # + testableSummaries: [ActionTestableSummary] + class ActionTestPlanRunSummary < ActionAbstractTestSummary + # @return [Array] testableSummaries + attr_reader :testable_summaries + + def initialize(data) + @testable_summaries = (data.dig('testableSummaries', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + end + end + + # - ActionTestRepetitionPolicySummary + # * Kind: object + # * Properties: + # + iteration: Int? + # + totalIterations: Int? + # + repetitionMode: String? + class ActionTestRepetitionPolicySummary + # @return [Int, nil] iteration + attr_reader :iteration + # @return [Int, nil] totalIterations + attr_reader :total_iterations + # @return [String, nil] repetitionMode + attr_reader :repetition_mode + + def initialize(data) + @iteration = data.dig('iteration', '_value').to_i if data['iteration'] + @total_iterations = data.dig('totalIterations', '_value').to_i if data['totalIterations'] + @repetition_mode = data.dig('repetitionMode', '_value') if data['repetitionMode'] + end + end + + # - ActionTestSummary + # * Supertype: ActionTestSummaryIdentifiableObject + # * Kind: object + # * Properties: + # + testStatus: String + # + duration: Double + # + performanceMetrics: [ActionTestPerformanceMetricSummary] + # + failureSummaries: [ActionTestFailureSummary] + # + expectedFailures: [ActionTestExpectedFailure] + # + skipNoticeSummary: ActionTestNoticeSummary? + # + activitySummaries: [ActionTestActivitySummary] + # + repetitionPolicySummary: ActionTestRepetitionPolicySummary? + class ActionTestSummary < ActionTestSummaryIdentifiableObject + # @return [String] testStatus + attr_reader :test_status + # @return [Double] duration + attr_reader :duration + # @return [Array] performanceMetrics + attr_reader :performance_metrics + # @return [Array] failureSummaries + attr_reader :failure_summaries + # @return [Array] expectedFailures + attr_reader :expected_failures + # @return [ActionTestNoticeSummary, nil] skipNoticeSummary + attr_reader :skip_notice_summary + # @return [Array] activitySummaries + attr_reader :activity_summaries + # @return [ActionTestRepetitionPolicySummary, nil] repetitionPolicySummary + attr_reader :repetition_policy_summary + + def initialize(data) + @test_status = data.dig('testStatus', '_value') + @duration = data.dig('duration', '_value').to_f + @performance_metrics = (data.dig('performanceMetrics', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + @failure_summaries = (data.dig('failureSummaries', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + @expected_failures = (data.dig('expectedFailures', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + @skip_notice_summary = Kernel.const_get("XCResult::Models::#{data.dig('skipNoticeSummary', '_type', '_name')}").new(data.dig('skipNoticeSummary')) if data['skipNoticeSummary'] + @activity_summaries = (data.dig('activitySummaries', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + @repetition_policy_summary = Kernel.const_get("XCResult::Models::#{data.dig('repetitionPolicySummary', '_type', '_name')}").new(data.dig('repetitionPolicySummary')) if data['repetitionPolicySummary'] + end + end + + # - ActionTestSummaryGroup + # * Supertype: ActionTestSummaryIdentifiableObject + # * Kind: object + # * Properties: + # + duration: Double + # + subtests: [ActionTestSummaryIdentifiableObject] + class ActionTestSummaryGroup < ActionTestSummaryIdentifiableObject + # @return [Double] duration + attr_reader :duration + # @return [Array] subtests + attr_reader :subtests + + def initialize(data) + @duration = data.dig('duration', '_value').to_f + @subtests = (data.dig('subtests', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + end + end + + # - ActionTestableSummary + # * Supertype: ActionAbstractTestSummary + # * Kind: object + # * Properties: + # + projectRelativePath: String? + # + targetName: String? + # + testKind: String? + # + tests: [ActionTestSummaryIdentifiableObject] + # + diagnosticsDirectoryName: String? + # + failureSummaries: [ActionTestFailureSummary] + # + testLanguage: String? + # + testRegion: String? + class ActionTestableSummary < ActionAbstractTestSummary + # @return [String, nil] projectRelativePath + attr_reader :project_relative_path + # @return [String, nil] targetName + attr_reader :target_name + # @return [String, nil] testKind + attr_reader :test_kind + # @return [Array] tests + attr_reader :tests + # @return [String, nil] diagnosticsDirectoryName + attr_reader :diagnostics_directory_name + # @return [Array] failureSummaries + attr_reader :failure_summaries + # @return [String, nil] testLanguage + attr_reader :test_language + # @return [String, nil] testRegion + attr_reader :test_region + + def initialize(data) + @project_relative_path = data.dig('projectRelativePath', '_value') if data['projectRelativePath'] + @target_name = data.dig('targetName', '_value') if data['targetName'] + @test_kind = data.dig('testKind', '_value') if data['testKind'] + @tests = (data.dig('tests', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + @diagnostics_directory_name = data.dig('diagnosticsDirectoryName', '_value') if data['diagnosticsDirectoryName'] + @failure_summaries = (data.dig('failureSummaries', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + @test_language = data.dig('testLanguage', '_value') if data['testLanguage'] + @test_region = data.dig('testRegion', '_value') if data['testRegion'] + end + end + + # - ActionsInvocationMetadata + # * Kind: object + # * Properties: + # + creatingWorkspaceFilePath: String + # + uniqueIdentifier: String + # + schemeIdentifier: EntityIdentifier? + class ActionsInvocationMetadata + # @return [String] creatingWorkspaceFilePath + attr_reader :creating_workspace_file_path + # @return [String] uniqueIdentifier + attr_reader :unique_identifier + # @return [EntityIdentifier, nil] schemeIdentifier + attr_reader :scheme_identifier + + def initialize(data) + @creating_workspace_file_path = data.dig('creatingWorkspaceFilePath', '_value') + @unique_identifier = data.dig('uniqueIdentifier', '_value') + @scheme_identifier = Kernel.const_get("XCResult::Models::#{data.dig('schemeIdentifier', '_type', '_name')}").new(data.dig('schemeIdentifier')) if data['schemeIdentifier'] + end + end + + # - ActionsInvocationRecord + # * Kind: object + # * Properties: + # + metadataRef: Reference? + # + metrics: ResultMetrics + # + issues: ResultIssueSummaries + # + actions: [ActionRecord] + # + archive: ArchiveInfo? + class ActionsInvocationRecord + # @return [Reference, nil] metadataRef + attr_reader :metadata_ref + # @return [ResultMetrics] metrics + attr_reader :metrics + # @return [ResultIssueSummaries] issues + attr_reader :issues + # @return [Array] actions + attr_reader :actions + # @return [ArchiveInfo, nil] archive + attr_reader :archive + + def initialize(data) + @metadata_ref = Kernel.const_get("XCResult::Models::#{data.dig('metadataRef', '_type', '_name')}").new(data.dig('metadataRef')) if data['metadataRef'] + @metrics = Kernel.const_get("XCResult::Models::#{data.dig('metrics', '_type', '_name')}").new(data.dig('metrics')) + @issues = Kernel.const_get("XCResult::Models::#{data.dig('issues', '_type', '_name')}").new(data.dig('issues')) + @actions = (data.dig('actions', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + @archive = Kernel.const_get("XCResult::Models::#{data.dig('archive', '_type', '_name')}").new(data.dig('archive')) if data['archive'] + end + end + + # - ActivityLogAnalyzerStep + # * Kind: object + # * Properties: + # + parentIndex: Int + class ActivityLogAnalyzerStep + # @return [Int] parentIndex + attr_reader :parent_index + + def initialize(data) + @parent_index = data.dig('parentIndex', '_value').to_i + end + end + + # - ActivityLogAnalyzerControlFlowStep + # * Supertype: ActivityLogAnalyzerStep + # * Kind: object + # * Properties: + # + title: String + # + startLocation: DocumentLocation? + # + endLocation: DocumentLocation? + # + edges: [ActivityLogAnalyzerControlFlowStepEdge] + class ActivityLogAnalyzerControlFlowStep < ActivityLogAnalyzerStep + # @return [String] title + attr_reader :title + # @return [DocumentLocation, nil] startLocation + attr_reader :start_location + # @return [DocumentLocation, nil] endLocation + attr_reader :end_location + # @return [Array] edges + attr_reader :edges + + def initialize(data) + @title = data.dig('title', '_value') + @start_location = Kernel.const_get("XCResult::Models::#{data.dig('startLocation', '_type', '_name')}").new(data.dig('startLocation')) if data['startLocation'] + @end_location = Kernel.const_get("XCResult::Models::#{data.dig('endLocation', '_type', '_name')}").new(data.dig('endLocation')) if data['endLocation'] + @edges = (data.dig('edges', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + end + end + + # - ActivityLogAnalyzerControlFlowStepEdge + # * Kind: object + # * Properties: + # + startLocation: DocumentLocation? + # + endLocation: DocumentLocation? + class ActivityLogAnalyzerControlFlowStepEdge + # @return [DocumentLocation, nil] startLocation + attr_reader :start_location + # @return [DocumentLocation, nil] endLocation + attr_reader :end_location + + def initialize(data) + @start_location = Kernel.const_get("XCResult::Models::#{data.dig('startLocation', '_type', '_name')}").new(data.dig('startLocation')) if data['startLocation'] + @end_location = Kernel.const_get("XCResult::Models::#{data.dig('endLocation', '_type', '_name')}").new(data.dig('endLocation')) if data['endLocation'] + end + end + + # - ActivityLogAnalyzerEventStep + # * Supertype: ActivityLogAnalyzerStep + # * Kind: object + # * Properties: + # + title: String + # + location: DocumentLocation? + # + description: String + # + callDepth: Int + class ActivityLogAnalyzerEventStep < ActivityLogAnalyzerStep + # @return [String] title + attr_reader :title + # @return [DocumentLocation, nil] location + attr_reader :location + # @return [String] description + attr_reader :description + # @return [Int] callDepth + attr_reader :call_depth + + def initialize(data) + @title = data.dig('title', '_value') + @location = Kernel.const_get("XCResult::Models::#{data.dig('location', '_type', '_name')}").new(data.dig('location')) if data['location'] + @description = data.dig('description', '_value') + @call_depth = data.dig('callDepth', '_value').to_i + end + end + + # - ActivityLogMessage + # * Kind: object + # * Properties: + # + type: String + # + title: String + # + shortTitle: String? + # + category: String? + # + location: DocumentLocation? + # + annotations: [ActivityLogMessageAnnotation] + class ActivityLogMessage + # @return [String] type + attr_reader :type + # @return [String] title + attr_reader :title + # @return [String, nil] shortTitle + attr_reader :short_title + # @return [String, nil] category + attr_reader :category + # @return [DocumentLocation, nil] location + attr_reader :location + # @return [Array] annotations + attr_reader :annotations + + def initialize(data) + @type = data.dig('type', '_value') + @title = data.dig('title', '_value') + @short_title = data.dig('shortTitle', '_value') if data['shortTitle'] + @category = data.dig('category', '_value') if data['category'] + @location = Kernel.const_get("XCResult::Models::#{data.dig('location', '_type', '_name')}").new(data.dig('location')) if data['location'] + @annotations = (data.dig('annotations', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + end + end + + # - ActivityLogAnalyzerResultMessage + # * Supertype: ActivityLogMessage + # * Kind: object + # * Properties: + # + steps: [ActivityLogAnalyzerStep] + # + resultType: String? + # + keyEventIndex: Int + class ActivityLogAnalyzerResultMessage < ActivityLogMessage + # @return [Array] steps + attr_reader :steps + # @return [String, nil] resultType + attr_reader :result_type + # @return [Int] keyEventIndex + attr_reader :key_event_index + + def initialize(data) + @steps = (data.dig('steps', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + @result_type = data.dig('resultType', '_value') if data['resultType'] + @key_event_index = data.dig('keyEventIndex', '_value').to_i + end + end + + # - ActivityLogAnalyzerWarningMessage + # * Supertype: ActivityLogMessage + # * Kind: object + class ActivityLogAnalyzerWarningMessage < ActivityLogMessage + + def initialize(data) + end + end + + # - ActivityLogSection + # * Kind: object + # * Properties: + # + domainType: String + # + title: String + # + startTime: Date? + # + duration: Double + # + result: String? + # + location: DocumentLocation? + # + subsections: [ActivityLogSection] + # + messages: [ActivityLogMessage] + class ActivityLogSection + # @return [String] domainType + attr_reader :domain_type + # @return [String] title + attr_reader :title + # @return [Date, nil] startTime + attr_reader :start_time + # @return [Double] duration + attr_reader :duration + # @return [String, nil] result + attr_reader :result + # @return [DocumentLocation, nil] location + attr_reader :location + # @return [Array] subsections + attr_reader :subsections + # @return [Array] messages + attr_reader :messages + + def initialize(data) + @domain_type = data.dig('domainType', '_value') + @title = data.dig('title', '_value') + @start_time = Time.parse(data.dig('startTime', '_value')) if data['startTime'] + @duration = data.dig('duration', '_value').to_f + @result = data.dig('result', '_value') if data['result'] + @location = Kernel.const_get("XCResult::Models::#{data.dig('location', '_type', '_name')}").new(data.dig('location')) if data['location'] + @subsections = (data.dig('subsections', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + @messages = (data.dig('messages', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + end + end + + # - ActivityLogCommandInvocationSection + # * Supertype: ActivityLogSection + # * Kind: object + # * Properties: + # + commandDetails: String + # + emittedOutput: String + # + exitCode: Int? + class ActivityLogCommandInvocationSection < ActivityLogSection + # @return [String] commandDetails + attr_reader :command_details + # @return [String] emittedOutput + attr_reader :emitted_output + # @return [Int, nil] exitCode + attr_reader :exit_code + + def initialize(data) + @command_details = data.dig('commandDetails', '_value') + @emitted_output = data.dig('emittedOutput', '_value') + @exit_code = data.dig('exitCode', '_value').to_i if data['exitCode'] + end + end + + # - ActivityLogMajorSection + # * Supertype: ActivityLogSection + # * Kind: object + # * Properties: + # + subtitle: String + class ActivityLogMajorSection < ActivityLogSection + # @return [String] subtitle + attr_reader :subtitle + + def initialize(data) + @subtitle = data.dig('subtitle', '_value') + end + end + + # - ActivityLogMessageAnnotation + # * Kind: object + # * Properties: + # + title: String + # + location: DocumentLocation? + class ActivityLogMessageAnnotation + # @return [String] title + attr_reader :title + # @return [DocumentLocation, nil] location + attr_reader :location + + def initialize(data) + @title = data.dig('title', '_value') + @location = Kernel.const_get("XCResult::Models::#{data.dig('location', '_type', '_name')}").new(data.dig('location')) if data['location'] + end + end + + # - ActivityLogTargetBuildSection + # * Supertype: ActivityLogMajorSection + # * Kind: object + # * Properties: + # + productType: String? + class ActivityLogTargetBuildSection < ActivityLogMajorSection + # @return [String, nil] productType + attr_reader :product_type + + def initialize(data) + @product_type = data.dig('productType', '_value') if data['productType'] + end + end + + # - ActivityLogUnitTestSection + # * Supertype: ActivityLogSection + # * Kind: object + # * Properties: + # + testName: String? + # + suiteName: String? + # + summary: String? + # + emittedOutput: String? + # + performanceTestOutput: String? + # + testsPassedString: String? + # + wasSkipped: Bool + # + runnablePath: String? + # + runnableUTI: String? + class ActivityLogUnitTestSection < ActivityLogSection + # @return [String, nil] testName + attr_reader :test_name + # @return [String, nil] suiteName + attr_reader :suite_name + # @return [String, nil] summary + attr_reader :summary + # @return [String, nil] emittedOutput + attr_reader :emitted_output + # @return [String, nil] performanceTestOutput + attr_reader :performance_test_output + # @return [String, nil] testsPassedString + attr_reader :tests_passed_string + # @return [Bool] wasSkipped + attr_reader :was_skipped + # @return [String, nil] runnablePath + attr_reader :runnable_path + # @return [String, nil] runnableUTI + attr_reader :runnable_uti + + def initialize(data) + @test_name = data.dig('testName', '_value') if data['testName'] + @suite_name = data.dig('suiteName', '_value') if data['suiteName'] + @summary = data.dig('summary', '_value') if data['summary'] + @emitted_output = data.dig('emittedOutput', '_value') if data['emittedOutput'] + @performance_test_output = data.dig('performanceTestOutput', '_value') if data['performanceTestOutput'] + @tests_passed_string = data.dig('testsPassedString', '_value') if data['testsPassedString'] + @was_skipped = data.dig('wasSkipped', '_value') + @runnable_path = data.dig('runnablePath', '_value') if data['runnablePath'] + @runnable_uti = data.dig('runnableUTI', '_value') if data['runnableUTI'] + end + end + + # - ArchiveInfo + # * Kind: object + # * Properties: + # + path: String? + class ArchiveInfo + # @return [String, nil] path + attr_reader :path + + def initialize(data) + @path = data.dig('path', '_value') if data['path'] + end + end + + # - Array + # * Kind: array + class Array + + def initialize(data) + end + end + + # - Bool + # * Kind: value + class Bool + + def initialize(data) + end + end + + # - CodeCoverageInfo + # * Kind: object + # * Properties: + # + hasCoverageData: Bool + # + reportRef: Reference? + # + archiveRef: Reference? + class CodeCoverageInfo + # @return [Bool] hasCoverageData + attr_reader :has_coverage_data + # @return [Reference, nil] reportRef + attr_reader :report_ref + # @return [Reference, nil] archiveRef + attr_reader :archive_ref + + def initialize(data) + @has_coverage_data = data.dig('hasCoverageData', '_value') + @report_ref = Kernel.const_get("XCResult::Models::#{data.dig('reportRef', '_type', '_name')}").new(data.dig('reportRef')) if data['reportRef'] + @archive_ref = Kernel.const_get("XCResult::Models::#{data.dig('archiveRef', '_type', '_name')}").new(data.dig('archiveRef')) if data['archiveRef'] + end + end + + # - Date + # * Kind: value + class Date + + def initialize(data) + end + end + + # - DocumentLocation + # * Kind: object + # * Properties: + # + url: String + # + concreteTypeName: String + class DocumentLocation + # @return [String] url + attr_reader :url + # @return [String] concreteTypeName + attr_reader :concrete_type_name + + def initialize(data) + @url = data.dig('url', '_value') + @concrete_type_name = data.dig('concreteTypeName', '_value') + end + end + + # - Double + # * Kind: value + class Double + + def initialize(data) + end + end + + # - EntityIdentifier + # * Kind: object + # * Properties: + # + entityName: String + # + containerName: String + # + entityType: String + # + sharedState: String + class EntityIdentifier + # @return [String] entityName + attr_reader :entity_name + # @return [String] containerName + attr_reader :container_name + # @return [String] entityType + attr_reader :entity_type + # @return [String] sharedState + attr_reader :shared_state + + def initialize(data) + @entity_name = data.dig('entityName', '_value') + @container_name = data.dig('containerName', '_value') + @entity_type = data.dig('entityType', '_value') + @shared_state = data.dig('sharedState', '_value') + end + end + + # - Int + # * Kind: value + class Int + + def initialize(data) + end + end + + # - IssueSummary + # * Kind: object + # * Properties: + # + issueType: String + # + message: String + # + producingTarget: String? + # + documentLocationInCreatingWorkspace: DocumentLocation? + class IssueSummary + # @return [String] issueType + attr_reader :issue_type + # @return [String] message + attr_reader :message + # @return [String, nil] producingTarget + attr_reader :producing_target + # @return [DocumentLocation, nil] documentLocationInCreatingWorkspace + attr_reader :document_location_in_creating_workspace + + def initialize(data) + @issue_type = data.dig('issueType', '_value') + @message = data.dig('message', '_value') + @producing_target = data.dig('producingTarget', '_value') if data['producingTarget'] + @document_location_in_creating_workspace = Kernel.const_get("XCResult::Models::#{data.dig('documentLocationInCreatingWorkspace', '_type', '_name')}").new(data.dig('documentLocationInCreatingWorkspace')) if data['documentLocationInCreatingWorkspace'] + end + end + + # - ObjectID + # * Kind: object + # * Properties: + # + hash: String + class ObjectID + # @return [String] hash + attr_reader :hash + + def initialize(data) + @hash = data.dig('hash', '_value') + end + end + + # - Reference + # * Kind: object + # * Properties: + # + id: String + # + targetType: TypeDefinition? + class Reference + # @return [String] id + attr_reader :id + # @return [TypeDefinition, nil] targetType + attr_reader :target_type + + def initialize(data) + @id = data.dig('id', '_value') + @target_type = Kernel.const_get("XCResult::Models::#{data.dig('targetType', '_type', '_name')}").new(data.dig('targetType')) if data['targetType'] + end + end + + # - ResultIssueSummaries + # * Kind: object + # * Properties: + # + analyzerWarningSummaries: [IssueSummary] + # + errorSummaries: [IssueSummary] + # + testFailureSummaries: [TestFailureIssueSummary] + # + warningSummaries: [IssueSummary] + class ResultIssueSummaries + # @return [Array] analyzerWarningSummaries + attr_reader :analyzer_warning_summaries + # @return [Array] errorSummaries + attr_reader :error_summaries + # @return [Array] testFailureSummaries + attr_reader :test_failure_summaries + # @return [Array] warningSummaries + attr_reader :warning_summaries + + def initialize(data) + @analyzer_warning_summaries = (data.dig('analyzerWarningSummaries', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + @error_summaries = (data.dig('errorSummaries', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + @test_failure_summaries = (data.dig('testFailureSummaries', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + @warning_summaries = (data.dig('warningSummaries', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + end + end + + # - ResultMetrics + # * Kind: object + # * Properties: + # + analyzerWarningCount: Int + # + errorCount: Int + # + testsCount: Int + # + testsFailedCount: Int + # + testsSkippedCount: Int + # + warningCount: Int + class ResultMetrics + # @return [Int] analyzerWarningCount + attr_reader :analyzer_warning_count + # @return [Int] errorCount + attr_reader :error_count + # @return [Int] testsCount + attr_reader :tests_count + # @return [Int] testsFailedCount + attr_reader :tests_failed_count + # @return [Int] testsSkippedCount + attr_reader :tests_skipped_count + # @return [Int] warningCount + attr_reader :warning_count + + def initialize(data) + @analyzer_warning_count = data.dig('analyzerWarningCount', '_value').to_i + @error_count = data.dig('errorCount', '_value').to_i + @tests_count = data.dig('testsCount', '_value').to_i + @tests_failed_count = data.dig('testsFailedCount', '_value').to_i + @tests_skipped_count = data.dig('testsSkippedCount', '_value').to_i + @warning_count = data.dig('warningCount', '_value').to_i + end + end + + # - SortedKeyValueArray + # * Kind: object + # * Properties: + # + storage: [SortedKeyValueArrayPair] + class SortedKeyValueArray + # @return [Array] storage + attr_reader :storage + + def initialize(data) + @storage = (data.dig('storage', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + end + end + + # - SortedKeyValueArrayPair + # * Kind: object + # * Properties: + # + key: String + # + value: SchemaSerializable + class SortedKeyValueArrayPair + # @return [String] key + attr_reader :key + # @return [SchemaSerializable] value + attr_reader :value + + def initialize(data) + @key = data.dig('key', '_value') + @value = data.dig('value', '_value') + end + end + + # - SourceCodeContext + # * Kind: object + # * Properties: + # + location: SourceCodeLocation? + # + callStack: [SourceCodeFrame] + class SourceCodeContext + # @return [SourceCodeLocation, nil] location + attr_reader :location + # @return [Array] callStack + attr_reader :call_stack + + def initialize(data) + @location = Kernel.const_get("XCResult::Models::#{data.dig('location', '_type', '_name')}").new(data.dig('location')) if data['location'] + @call_stack = (data.dig('callStack', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + end + end + + # - SourceCodeFrame + # * Kind: object + # * Properties: + # + addressString: String? + # + symbolInfo: SourceCodeSymbolInfo? + class SourceCodeFrame + # @return [String, nil] addressString + attr_reader :address_string + # @return [SourceCodeSymbolInfo, nil] symbolInfo + attr_reader :symbol_info + + def initialize(data) + @address_string = data.dig('addressString', '_value') if data['addressString'] + @symbol_info = Kernel.const_get("XCResult::Models::#{data.dig('symbolInfo', '_type', '_name')}").new(data.dig('symbolInfo')) if data['symbolInfo'] + end + end + + # - SourceCodeLocation + # * Kind: object + # * Properties: + # + filePath: String? + # + lineNumber: Int? + class SourceCodeLocation + # @return [String, nil] filePath + attr_reader :file_path + # @return [Int, nil] lineNumber + attr_reader :line_number + + def initialize(data) + @file_path = data.dig('filePath', '_value') if data['filePath'] + @line_number = data.dig('lineNumber', '_value').to_i if data['lineNumber'] + end + end + + # - SourceCodeSymbolInfo + # * Kind: object + # * Properties: + # + imageName: String? + # + symbolName: String? + # + location: SourceCodeLocation? + class SourceCodeSymbolInfo + # @return [String, nil] imageName + attr_reader :image_name + # @return [String, nil] symbolName + attr_reader :symbol_name + # @return [SourceCodeLocation, nil] location + attr_reader :location + + def initialize(data) + @image_name = data.dig('imageName', '_value') if data['imageName'] + @symbol_name = data.dig('symbolName', '_value') if data['symbolName'] + @location = Kernel.const_get("XCResult::Models::#{data.dig('location', '_type', '_name')}").new(data.dig('location')) if data['location'] + end + end + + # - String + # * Kind: value + class String + + def initialize(data) + end + end + + # - TestAssociatedError + # * Kind: object + # * Properties: + # + domain: String? + # + code: Int? + # + userInfo: SortedKeyValueArray? + class TestAssociatedError + # @return [String, nil] domain + attr_reader :domain + # @return [Int, nil] code + attr_reader :code + # @return [SortedKeyValueArray, nil] userInfo + attr_reader :user_info + + def initialize(data) + @domain = data.dig('domain', '_value') if data['domain'] + @code = data.dig('code', '_value').to_i if data['code'] + @user_info = Kernel.const_get("XCResult::Models::#{data.dig('userInfo', '_type', '_name')}").new(data.dig('userInfo')) if data['userInfo'] + end + end + + # - TestFailureIssueSummary + # * Supertype: IssueSummary + # * Kind: object + # * Properties: + # + testCaseName: String + class TestFailureIssueSummary < IssueSummary + # @return [String] testCaseName + attr_reader :test_case_name + + def initialize(data) + @test_case_name = data.dig('testCaseName', '_value') + end + end + + # - TypeDefinition + # * Kind: object + # * Properties: + # + name: String + # + supertype: TypeDefinition? + class TypeDefinition + # @return [String] name + attr_reader :name + # @return [TypeDefinition, nil] supertype + attr_reader :supertype + + def initialize(data) + @name = data.dig('name', '_value') + @supertype = Kernel.const_get("XCResult::Models::#{data.dig('supertype', '_type', '_name')}").new(data.dig('supertype')) if data['supertype'] + end + end + + end +end diff --git a/lib/xcresult/models.rb b/lib/xcresult/models.rb index abbfc7b..106f03c 100644 --- a/lib/xcresult/models.rb +++ b/lib/xcresult/models.rb @@ -1,550 +1,71 @@ # frozen_string_literal: true -require 'xcresult/version' -require 'time' -module XCResult - # Model attributes and relationships taken from running the following command: - # xcrun xcresulttool formatDescription - - class AbstractObject - attr_accessor :type - def initialize(data) - self.type = data['_type']['_name'] - end - - def fetch_value(data, key) - (data[key] || {})['_value'] - end - - def fetch_values(data, key) - return [] if data.nil? +require 'xcresult/models.gen' - (data[key] || {})['_values'] || [] - end - end - - # - ActionTestPlanRunSummaries - # * Kind: object - # * Properties: - # + summaries: [ActionTestPlanRunSummary] - class ActionTestPlanRunSummaries < AbstractObject - attr_accessor :summaries - def initialize(data) - self.summaries = fetch_values(data, 'summaries').map do |summary_data| - ActionTestPlanRunSummary.new(summary_data) +# This file is meant to define extensions to generated models in models.gen.rb. +module XCResult + module Models + class ActionTestableSummary + def all_tests + tests.map(&:all_subtests).flatten end - super end - end - # - ActionAbstractTestSummary - # * Kind: object - # * Properties: - # + name: String? - class ActionAbstractTestSummary < AbstractObject - attr_accessor :name - def initialize(data) - self.name = fetch_value(data, 'name') - super - end - end - - # - ActionTestPlanRunSummary - # * Supertype: ActionAbstractTestSummary - # * Kind: object - # * Properties: - # + testableSummaries: [ActionTestableSummary] - class ActionTestPlanRunSummary < ActionAbstractTestSummary - attr_accessor :testable_summaries - def initialize(data) - self.testable_summaries = fetch_values(data, 'testableSummaries').map do |summary_data| - ActionTestableSummary.new(summary_data) + class ActionTestSummaryIdentifiableObject + def all_subtests + raise 'Not overridden' end - super end - end - # - ActionTestableSummary - # * Supertype: ActionAbstractTestSummary - # * Kind: object - # * Properties: - # + projectRelativePath: String? - # + targetName: String? - # + testKind: String? - # + tests: [ActionTestSummaryIdentifiableObject] - # + diagnosticsDirectoryName: String? - # + failureSummaries: [ActionTestFailureSummary] - # + testLanguage: String? - # + testRegion: String? - class ActionTestableSummary < ActionAbstractTestSummary - attr_accessor :project_relative_path - attr_accessor :target_name - attr_accessor :test_kind - attr_accessor :tests - def initialize(data) - self.project_relative_path = fetch_value(data, 'projectRelativePath') - self.target_name = fetch_value(data, 'targetName') - self.test_kind = fetch_value(data, 'testKind') - self.tests = fetch_values(data, 'tests').map do |tests_data| - ActionTestSummaryIdentifiableObject.create(tests_data, self) + class ActionTestSummaryGroup + def all_subtests + subtests.map(&:all_subtests).flatten end - super end - def all_tests - tests.map(&:all_subtests).flatten - end - end - - # - ActionTestSummaryIdentifiableObject - # * Supertype: ActionAbstractTestSummary - # * Kind: object - # * Properties: - # + identifier: String? - class ActionTestSummaryIdentifiableObject < ActionAbstractTestSummary - attr_accessor :identifier - attr_accessor :parent - def initialize(data, parent) - self.identifier = fetch_value(data, 'identifier') - self.parent = parent - super(data) - end - - def all_subtests - raise 'Not overridden' - end - - def self.create(data, parent) - type = data['_type']['_name'] - if type == 'ActionTestSummaryGroup' - return ActionTestSummaryGroup.new(data, parent) - elsif type == 'ActionTestSummary' - return ActionTestSummary.new(data, parent) - elsif type == 'ActionTestMetadata' - return ActionTestMetadata.new(data, parent) - else - raise "Unsupported type: #{type}" + class ActionTestMetadata + def all_subtests + [self] end - end - end - - # - ActionTestSummaryGroup - # * Supertype: ActionTestSummaryIdentifiableObject - # * Kind: object - # * Properties: - # + duration: Double - # + subtests: [ActionTestSummaryIdentifiableObject] - class ActionTestSummaryGroup < ActionTestSummaryIdentifiableObject - attr_accessor :duration - attr_accessor :subtests - def initialize(data, parent) - self.duration = fetch_value(data, 'duration').to_f - self.subtests = fetch_values(data, 'subtests').map do |subtests_data| - ActionTestSummaryIdentifiableObject.create(subtests_data, self) - end - super(data, parent) - end - - def all_subtests - subtests.map(&:all_subtests).flatten - end - end - - # - ActionTestMetadata - # * Supertype: ActionTestSummaryIdentifiableObject - # * Kind: object - # * Properties: - # + testStatus: String - # + duration: Double? - # + summaryRef: Reference? - # + performanceMetricsCount: Int - # + failureSummariesCount: Int - # + activitySummariesCount: Int - class ActionTestMetadata < ActionTestSummaryIdentifiableObject - attr_accessor :test_status - attr_accessor :duration - attr_accessor :summary_ref - attr_accessor :performance_metrics_count - attr_accessor :failure_summaries_count - attr_accessor :activity_summaries_count - def initialize(data, parent) - self.test_status = fetch_value(data, 'testStatus') - self.duration = fetch_value(data, 'duration').to_f - self.summary_ref = Reference.new(data['summaryRef']) if data['summaryRef'] - self.performance_metrics_count = fetch_value(data, 'performanceMetricsCount') - self.failure_summaries_count = fetch_value(data, 'failureSummariesCount') - self.activity_summaries_count = fetch_value(data, 'activitySummariesCount') - super(data, parent) - end - - def all_subtests - [self] - end - def find_failure(failures) - if test_status == 'Failure' - # Tries to match failure on test case name - # Example TestFailureIssueSummary: - # producingTarget: "TestThisDude" - # test_case_name: "TestThisDude.testFailureJosh2()" (when Swift) - # or "-[TestThisDudeTests testFailureJosh2]" (when Objective-C) - # Example ActionTestMetadata - # identifier: "TestThisDude/testFailureJosh2()" (when Swift) - # or identifier: "TestThisDude/testFailureJosh2" (when Objective-C) + def find_failure(failures) + if test_status == 'Failure' + # Tries to match failure on test case name + # Example TestFailureIssueSummary: + # producingTarget: "TestThisDude" + # test_case_name: "TestThisDude.testFailureJosh2()" (when Swift) + # or "-[TestThisDudeTests testFailureJosh2]" (when Objective-C) + # Example ActionTestMetadata + # identifier: "TestThisDude/testFailureJosh2()" (when Swift) + # or identifier: "TestThisDude/testFailureJosh2" (when Objective-C) - found_failure = failures.find do |failure| - # Clean test_case_name to match identifier format - # Sanitize for Swift by replacing "." for "/" - # Sanitize for Objective-C by removing "-", "[", "]", and replacing " " for ?/ - sanitized_test_case_name = failure.test_case_name - .tr('.', '/') - .tr('-', '') - .tr('[', '') - .tr(']', '') - .tr(' ', '/') - identifier == sanitized_test_case_name + found_failure = failures.find do |failure| + # Clean test_case_name to match identifier format + # Sanitize for Swift by replacing "." for "/" + # Sanitize for Objective-C by removing "-", "[", "]", and replacing " " for ?/ + sanitized_test_case_name = failure.test_case_name + .tr('.', '/') + .tr('-', '') + .tr('[', '') + .tr(']', '') + .tr(' ', '/') + identifier == sanitized_test_case_name + end + found_failure end - found_failure - end - end - end - - # - ActionsInvocationRecord - # * Kind: object - # * Properties: - # + metadataRef: Reference? - # + metrics: ResultMetrics - # + issues: ResultIssueSummaries - # + actions: [ActionRecord] - # + archive: ArchiveInfo? - class ActionsInvocationRecord < AbstractObject - attr_accessor :actions - attr_accessor :issues - def initialize(data) - self.actions = fetch_values(data, 'actions').map do |action_data| - ActionRecord.new(action_data) - end - self.issues = ResultIssueSummaries.new(data['issues']) - super - end - end - - # - ActionRecord - # * Kind: object - # * Properties: - # + schemeCommandName: String - # + schemeTaskName: String - # + title: String? - # + startedTime: Date - # + endedTime: Date - # + runDestination: ActionRunDestinationRecord - # + buildResult: ActionResult - # + actionResult: ActionResult - class ActionRecord < AbstractObject - attr_accessor :scheme_command_name - attr_accessor :scheme_task_name - attr_accessor :title - attr_accessor :build_result - attr_accessor :action_result - def initialize(data) - self.scheme_command_name = fetch_value(data, 'schemeCommandName') - self.scheme_task_name = fetch_value(data, 'schemeTaskName') - self.title = fetch_value(data, 'title') - self.build_result = ActionResult.new(data['buildResult']) - self.action_result = ActionResult.new(data['actionResult']) - super - end - end - - # - ActionResult - # * Kind: object - # * Properties: - # + resultName: String - # + status: String - # + metrics: ResultMetrics - # + issues: ResultIssueSummaries - # + coverage: CodeCoverageInfo - # + timelineRef: Reference? - # + logRef: Reference? - # + testsRef: Reference? - # + diagnosticsRef: Reference? - class ActionResult < AbstractObject - attr_accessor :result_name - attr_accessor :status - attr_accessor :issues - attr_accessor :coverage - attr_accessor :timeline_ref - attr_accessor :log_ref - attr_accessor :tests_ref - attr_accessor :diagnostics_ref - def initialize(data) - self.result_name = fetch_value(data, 'resultName') - self.status = fetch_value(data, 'status') - self.issues = ResultIssueSummaries.new(data['issues']) - self.coverage = CodeCoverageInfo.new(data['coverage']) - - self.timeline_ref = Reference.new(data['timelineRef']) if data['timelineRef'] - self.log_ref = Reference.new(data['logRef']) if data['logRef'] - self.tests_ref = Reference.new(data['testsRef']) if data['testsRef'] - self.diagnostics_ref = Reference.new(data['diagnosticsRef']) if data['diagnosticsRef'] - super - end - end - - # - CodeCoverageInfo - # * Kind: object - # * Properties: - # + hasCoverageData: Bool - # + reportRef: Reference? - # + archiveRef: Reference? - class CodeCoverageInfo < AbstractObject - attr_accessor :has_coverage_data - attr_accessor :report_ref - attr_accessor :archive_ref - def initialize(data) - self.has_coverage_data = fetch_value(data, 'hasCoverageData') - self.report_ref = Reference.new(data['reportRef']) if data['reportRef'] - self.archive_ref = Reference.new(data['archiveRef']) if data['archiveRef'] - end - end - - # - Reference - # * Kind: object - # * Properties: - # + id: String - # + targetType: TypeDefinition? - class Reference < AbstractObject - attr_accessor :id - attr_accessor :target_type - def initialize(data) - self.id = fetch_value(data, 'id') - self.target_type = TypeDefinition.new(data['targetType']) if data['targetType'] - super - end - end - - # - TypeDefinition - # * Kind: object - # * Properties: - # + name: String - # + supertype: TypeDefinition? - class TypeDefinition < AbstractObject - attr_accessor :name - attr_accessor :supertype - def initialize(data) - self.name = fetch_value(data, 'name') - self.supertype = TypeDefinition.new(data['supertype']) if data['supertype'] - super - end - end - - # - DocumentLocation - # * Kind: object - # * Properties: - # + url: String - # + concreteTypeName: String - class DocumentLocation < AbstractObject - attr_accessor :url - attr_accessor :concrete_type_name - def initialize(data) - self.url = fetch_value(data, 'url') - self.concrete_type_name = data['concreteTypeName']['_value'] - super - end - end - - # - IssueSummary - # * Kind: object - # * Properties: - # + issueType: String - # + message: String - # + producingTarget: String? - # + documentLocationInCreatingWorkspace: DocumentLocation? - class IssueSummary < AbstractObject - attr_accessor :issue_type - attr_accessor :message - attr_accessor :producing_target - attr_accessor :document_location_in_creating_workspace - def initialize(data) - self.issue_type = fetch_value(data, 'issueType') - self.message = fetch_value(data, 'message') - self.producing_target = fetch_value(data, 'producingTarget') - self.document_location_in_creating_workspace = DocumentLocation.new(data['documentLocationInCreatingWorkspace']) if data['documentLocationInCreatingWorkspace'] - super - end - end - - # - ResultIssueSummaries - # * Kind: object - # * Properties: - # + analyzerWarningSummaries: [IssueSummary] - # + errorSummaries: [IssueSummary] - # + testFailureSummaries: [TestFailureIssueSummary] - # + warningSummaries: [IssueSummary] - class ResultIssueSummaries < AbstractObject - attr_accessor :analyzer_warning_summaries - attr_accessor :error_summaries - attr_accessor :test_failure_summaries - attr_accessor :warning_summaries - def initialize(data) - self.analyzer_warning_summaries = fetch_values(data, 'analyzerWarningSummaries').map do |summary_data| - IssueSummary.new(summary_data) - end - self.error_summaries = fetch_values(data, 'errorSummaries').map do |summary_data| - IssueSummary.new(summary_data) - end - self.test_failure_summaries = fetch_values(data, 'testFailureSummaries').map do |summary_data| - TestFailureIssueSummary.new(summary_data) end - self.warning_summaries = fetch_values(data, 'warningSummaries').map do |summary_data| - IssueSummary.new(summary_data) - end - super end - end - # - TestFailureIssueSummary - # * Supertype: IssueSummary - # * Kind: object - # * Properties: - # + testCaseName: String - class TestFailureIssueSummary < IssueSummary - attr_accessor :test_case_name - def initialize(data) - self.test_case_name = fetch_value(data, 'testCaseName') - super - end - - def failure_message - new_message = message - if document_location_in_creating_workspace - file_path = document_location_in_creating_workspace.url.gsub('file://', '') - new_message += " (#{file_path})" - end - - new_message - end - end - - # - ActionTestSummary - # * Supertype: ActionTestSummaryIdentifiableObject - # * Kind: object - # * Properties: - # + testStatus: String - # + duration: Double - # + performanceMetrics: [ActionTestPerformanceMetricSummary] - # + failureSummaries: [ActionTestFailureSummary] - # + activitySummaries: [ActionTestActivitySummary] - class ActionTestSummary < ActionTestSummaryIdentifiableObject - attr_accessor :test_status - attr_accessor :duration - attr_accessor :activity_summaries - def initialize(data, parent = nil) - self.test_status = fetch_value(data, 'testStatus') - self.duration = fetch_value(data, 'duration').to_f - self.activity_summaries = fetch_values(data, 'activitySummaries').map do |summary_data| - ActionTestActivitySummary.new(summary_data) - end - super(data, parent) - end - end - - # - ActionTestActivitySummary - # * Kind: object - # * Properties: - # + title: String - # + activityType: String - # + uuid: String - # + start: Date? - # + finish: Date? - # + attachments: [ActionTestAttachment] - # + subactivities: [ActionTestActivitySummary] - class ActionTestActivitySummary < AbstractObject - attr_accessor :title - attr_accessor :activity_type - attr_accessor :uuid - attr_accessor :start - attr_accessor :finish - attr_accessor :attachments - attr_accessor :subactivities - def initialize(data) - self.title = fetch_value(data, 'title') - self.activity_type = fetch_value(data, 'activityType') - self.uuid = fetch_value(data, 'uuid') - self.start = Time.parse(fetch_value(data, 'start')) if data['start'] - self.finish = Time.parse(fetch_value(data, 'finish')) if data['finish'] - self.attachments = fetch_values(data, 'attachments').map do |attachment_data| - ActionTestAttachment.new(attachment_data) - end - self.subactivities = fetch_values(data, 'subactivities').map do |summary_data| - ActionTestActivitySummary.new(summary_data) - end - super(data) - end - end - - # - ActionTestAttachment - # * Kind: object - # * Properties: - # + uniformTypeIdentifier: String - # + name: String? - # + timestamp: Date? - # + userInfo: SortedKeyValueArray? - # + lifetime: String - # + inActivityIdentifier: Int - # + filename: String? - # + payloadRef: Reference? - # + payloadSize: Int - class ActionTestAttachment < AbstractObject - attr_accessor :uniform_type_identifier - attr_accessor :name - attr_accessor :timestamp - attr_accessor :user_info - attr_accessor :lifetime - attr_accessor :in_activity_identifier - attr_accessor :filename - attr_accessor :payload_ref - attr_accessor :payload_size - def initialize(data) - self.uniform_type_identifier = fetch_value(data, 'uniformTypeIdentifier') - self.name = fetch_value(data, 'name') - self.timestamp = Time.parse(fetch_value(data, 'timestamp')) if data['timestamp'] - self.user_info = SortedKeyValueArray.new(data['userInfo']) if data['userInfo'] - self.lifetime = fetch_value(data, 'lifetime') - self.in_activity_identifier = fetch_value(data, 'inActivityIdentifier').to_i - self.filename = fetch_value(data, 'filename') - self.payload_ref = Reference.new(data['payloadRef']) if data['payloadRef'] - self.payload_size = fetch_value(data, 'payloadSize').to_i - super(data) - end - end + class TestFailureIssueSummary + def failure_message + new_message = message + if document_location_in_creating_workspace + file_path = document_location_in_creating_workspace.url.gsub('file://', '') + new_message += " (#{file_path})" + end - # - SortedKeyValueArray - # * Kind: object - # * Properties: - # + storage: [SortedKeyValueArrayPair] - class SortedKeyValueArray < AbstractObject - attr_accessor :storage - def initialize(data) - self.storage = fetch_values(data, 'storage').map do |pair_data| - SortedKeyValueArrayPair.new(pair_data) + new_message end - super(data) - end - end - - # - SortedKeyValueArrayPair - # * Kind: object - # * Properties: - # + key: String - # + value: SchemaSerializable - class SortedKeyValueArrayPair < AbstractObject - attr_accessor :key - attr_accessor :value - def initialize(data) - self.key = fetch_value(data, 'key') - self.value = fetch_value(data, 'value') - super(data) end end end diff --git a/lib/xcresult/parser.rb b/lib/xcresult/parser.rb index 0a7741e..c064c34 100644 --- a/lib/xcresult/parser.rb +++ b/lib/xcresult/parser.rb @@ -13,7 +13,7 @@ def initialize(path: nil) result_bundle_json_raw = get_result_bundle_json @result_bundle_json = JSON.parse(result_bundle_json_raw) - @actions_invocation_record = XCResult::ActionsInvocationRecord.new(@result_bundle_json) + @actions_invocation_record = XCResult::Models::ActionsInvocationRecord.new(@result_bundle_json) end def action_test_plan_summaries @@ -30,7 +30,7 @@ def action_test_plan_summaries @action_test_plan_summaries = ids.map do |id| raw = execute_cmd("xcrun xcresulttool get --format json --path #{path} --id #{id}") json = JSON.parse(raw) - XCResult::ActionTestPlanRunSummaries.new(json) + XCResult::Models::ActionTestPlanRunSummaries.new(json) end @action_test_plan_summaries From 33bdcc0867279730ebcc636c4de360bf89194d24 Mon Sep 17 00:00:00 2001 From: ainame Date: Wed, 21 Jul 2021 07:23:45 +0900 Subject: [PATCH 08/24] Add missing super for subclasses --- lib/xcresult/model_generator.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/xcresult/model_generator.rb b/lib/xcresult/model_generator.rb index a2e01d8..275dfa5 100644 --- a/lib/xcresult/model_generator.rb +++ b/lib/xcresult/model_generator.rb @@ -78,6 +78,9 @@ class <%= type.name %> def initialize(data) <% type.properties.each do |property| %> @<%= property.name_in_snake_case %> = <%= property.mapping(type_to_kind[property.main_type], 'data') %> + <% end %> + <% if type.supertype %> + super <% end %> end end From 8f4ae56ea7fdfb38081095eb795a8c2bb7661324 Mon Sep 17 00:00:00 2001 From: ainame Date: Wed, 21 Jul 2021 07:24:08 +0900 Subject: [PATCH 09/24] Update models --- lib/xcresult/models.gen.rb | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/xcresult/models.gen.rb b/lib/xcresult/models.gen.rb index 9738aba..50d355a 100644 --- a/lib/xcresult/models.gen.rb +++ b/lib/xcresult/models.gen.rb @@ -1,5 +1,5 @@ # This is a generated file. Don't modify this directly! -# Last generated at: 2021-07-20 22:18:47 UTC +# Last generated at: 2021-07-20 22:23:35 UTC # # Name: Xcode Result Types # Version: 3.30 @@ -443,6 +443,7 @@ class ActionTestSummaryIdentifiableObject < ActionAbstractTestSummary def initialize(data) @identifier = data.dig('identifier', '_value') if data['identifier'] + super end end @@ -477,6 +478,7 @@ def initialize(data) @performance_metrics_count = data.dig('performanceMetricsCount', '_value').to_i @failure_summaries_count = data.dig('failureSummariesCount', '_value').to_i @activity_summaries_count = data.dig('activitySummariesCount', '_value').to_i + super end end @@ -578,6 +580,7 @@ class ActionTestPlanRunSummary < ActionAbstractTestSummary def initialize(data) @testable_summaries = (data.dig('testableSummaries', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + super end end @@ -641,6 +644,7 @@ def initialize(data) @skip_notice_summary = Kernel.const_get("XCResult::Models::#{data.dig('skipNoticeSummary', '_type', '_name')}").new(data.dig('skipNoticeSummary')) if data['skipNoticeSummary'] @activity_summaries = (data.dig('activitySummaries', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } @repetition_policy_summary = Kernel.const_get("XCResult::Models::#{data.dig('repetitionPolicySummary', '_type', '_name')}").new(data.dig('repetitionPolicySummary')) if data['repetitionPolicySummary'] + super end end @@ -659,6 +663,7 @@ class ActionTestSummaryGroup < ActionTestSummaryIdentifiableObject def initialize(data) @duration = data.dig('duration', '_value').to_f @subtests = (data.dig('subtests', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + super end end @@ -701,6 +706,7 @@ def initialize(data) @failure_summaries = (data.dig('failureSummaries', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } @test_language = data.dig('testLanguage', '_value') if data['testLanguage'] @test_region = data.dig('testRegion', '_value') if data['testRegion'] + super end end @@ -790,6 +796,7 @@ def initialize(data) @start_location = Kernel.const_get("XCResult::Models::#{data.dig('startLocation', '_type', '_name')}").new(data.dig('startLocation')) if data['startLocation'] @end_location = Kernel.const_get("XCResult::Models::#{data.dig('endLocation', '_type', '_name')}").new(data.dig('endLocation')) if data['endLocation'] @edges = (data.dig('edges', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + super end end @@ -833,6 +840,7 @@ def initialize(data) @location = Kernel.const_get("XCResult::Models::#{data.dig('location', '_type', '_name')}").new(data.dig('location')) if data['location'] @description = data.dig('description', '_value') @call_depth = data.dig('callDepth', '_value').to_i + super end end @@ -888,6 +896,7 @@ def initialize(data) @steps = (data.dig('steps', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } @result_type = data.dig('resultType', '_value') if data['resultType'] @key_event_index = data.dig('keyEventIndex', '_value').to_i + super end end @@ -897,6 +906,7 @@ def initialize(data) class ActivityLogAnalyzerWarningMessage < ActivityLogMessage def initialize(data) + super end end @@ -960,6 +970,7 @@ def initialize(data) @command_details = data.dig('commandDetails', '_value') @emitted_output = data.dig('emittedOutput', '_value') @exit_code = data.dig('exitCode', '_value').to_i if data['exitCode'] + super end end @@ -974,6 +985,7 @@ class ActivityLogMajorSection < ActivityLogSection def initialize(data) @subtitle = data.dig('subtitle', '_value') + super end end @@ -1005,6 +1017,7 @@ class ActivityLogTargetBuildSection < ActivityLogMajorSection def initialize(data) @product_type = data.dig('productType', '_value') if data['productType'] + super end end @@ -1051,6 +1064,7 @@ def initialize(data) @was_skipped = data.dig('wasSkipped', '_value') @runnable_path = data.dig('runnablePath', '_value') if data['runnablePath'] @runnable_uti = data.dig('runnableUTI', '_value') if data['runnableUTI'] + super end end @@ -1425,6 +1439,7 @@ class TestFailureIssueSummary < IssueSummary def initialize(data) @test_case_name = data.dig('testCaseName', '_value') + super end end From 0c34d5edf9d7b95df974efe437e2de0046e3cfc6 Mon Sep 17 00:00:00 2001 From: ainame Date: Wed, 21 Jul 2021 09:52:19 +0900 Subject: [PATCH 10/24] Rename source -> format --- lib/xcresult/model_generator.rb | 38 ++++++++++++++++----------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/xcresult/model_generator.rb b/lib/xcresult/model_generator.rb index 275dfa5..66ed208 100644 --- a/lib/xcresult/model_generator.rb +++ b/lib/xcresult/model_generator.rb @@ -9,16 +9,16 @@ def self.format_description end def self.generate(destination) - parser = Parser.new(source: format_description) + parser = Parser.new(format: format_description) parser.parse - generator = Generator.new(parser.source) + generator = Generator.new(parser.format) generator.write(destination) end class Generator - def initialize(source) - @source = source - @type_to_kind = source.types.map { |t| [t.name, t.kind] }.to_h + def initialize(format) + @format = format + @type_to_kind = format.types.map { |t| [t.name, t.kind] }.to_h end def write(destination) @@ -27,9 +27,9 @@ def write(destination) # This is a generated file. Don't modify this directly! # Last generated at: #{Time.now.utc} # - # #{@source.name} - # #{@source.version} - # #{@source.signature} + # #{@format.name} + # #{@format.version} + # #{@format.signature} require 'time' HEADER @@ -56,7 +56,7 @@ module Models # Use topological sort to correctly defines classes that may depend on another as its super class def sorted_types - h = @source.types.map {|x| [x, [@source.types.find {|y| y.name == x.supertype}].compact] }.to_h + h = @format.types.map {|x| [x, [@format.types.find {|y| y.name == x.supertype}].compact] }.to_h each_node = ->(&b) { h.each_key(&b) } each_child = ->(n, &b) { h[n].each(&b) } TSort.tsort(each_node, each_child) @@ -91,18 +91,18 @@ def initialize(data) end class Parser - attr_reader :source + attr_reader :format - def initialize(source: ) - @raw_source = source - @lines = source.each_line.to_a - @source = Source.new + def initialize(format:) + @raw_format = format + @lines = format.each_line.to_a + @format = Format.new end def parse - @source.name = @lines.shift.chomp - @source.version = @lines.shift.chomp - @source.signature = @lines.shift.chomp + @format.name = @lines.shift.chomp + @format.version = @lines.shift.chomp + @format.signature = @lines.shift.chomp # Drop "- Types" lines @lines.shift @@ -130,7 +130,7 @@ def parse end types << type if type - @source.types = types + @format.types = types end def parse_properties(type) @@ -154,7 +154,7 @@ def parse_properties(type) end end - Source = Struct.new(:name, :version, :signature, :types, keyword_init: true) + Format = Struct.new(:name, :version, :signature, :types, keyword_init: true) Type = Struct.new(:name, :supertype, :kind, :properties, :raw_text, keyword_init: true) Property = Struct.new(:name, :type, :main_type, :optional, :array, keyword_init: true) do def name_in_snake_case From 1e7457f67e0d48e5b9d22881cd824e266a22def2 Mon Sep 17 00:00:00 2001 From: ainame Date: Wed, 21 Jul 2021 10:02:52 +0900 Subject: [PATCH 11/24] Fix conversion of camel case to snake_case --- lib/xcresult/model_generator.rb | 5 ++++- lib/xcresult/models.gen.rb | 14 +++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/xcresult/model_generator.rb b/lib/xcresult/model_generator.rb index 66ed208..573aa9c 100644 --- a/lib/xcresult/model_generator.rb +++ b/lib/xcresult/model_generator.rb @@ -158,7 +158,10 @@ def parse_properties(type) Type = Struct.new(:name, :supertype, :kind, :properties, :raw_text, keyword_init: true) Property = Struct.new(:name, :type, :main_type, :optional, :array, keyword_init: true) do def name_in_snake_case - name.gsub(/([a-z])(?=[A-Z])/) { (Regexp.last_match[1] || Regexp.last_match[2]) << '_' }.downcase + name + .gsub(/(CPU|ID|MHz|UTI|SDK)/) { Regexp.last_match[1].downcase.capitalize } # normalize acronyms + .gsub(/([a-z])(?=[A-Z])/) { (Regexp.last_match[1] || Regexp.last_match[2]) << '_' } + .downcase end def mapping(kind, variable_name) diff --git a/lib/xcresult/models.gen.rb b/lib/xcresult/models.gen.rb index 50d355a..17d5e85 100644 --- a/lib/xcresult/models.gen.rb +++ b/lib/xcresult/models.gen.rb @@ -1,5 +1,5 @@ # This is a generated file. Don't modify this directly! -# Last generated at: 2021-07-20 22:23:35 UTC +# Last generated at: 2021-07-21 01:01:36 UTC # # Name: Xcode Result Types # Version: 3.30 @@ -75,9 +75,9 @@ class ActionDeviceRecord # @return [Int, nil] ramSizeInMegabytes attr_reader :ram_size_in_megabytes # @return [Int, nil] physicalCPUCoresPerPackage - attr_reader :physical_cpucores_per_package + attr_reader :physical_cpu_cores_per_package # @return [Int, nil] logicalCPUCoresPerPackage - attr_reader :logical_cpucores_per_package + attr_reader :logical_cpu_cores_per_package # @return [ActionPlatformRecord] platformRecord attr_reader :platform_record @@ -97,8 +97,8 @@ def initialize(data) @cpu_speed_in_mhz = data.dig('cpuSpeedInMHz', '_value').to_i if data['cpuSpeedInMHz'] @bus_speed_in_mhz = data.dig('busSpeedInMHz', '_value').to_i if data['busSpeedInMHz'] @ram_size_in_megabytes = data.dig('ramSizeInMegabytes', '_value').to_i if data['ramSizeInMegabytes'] - @physical_cpucores_per_package = data.dig('physicalCPUCoresPerPackage', '_value').to_i if data['physicalCPUCoresPerPackage'] - @logical_cpucores_per_package = data.dig('logicalCPUCoresPerPackage', '_value').to_i if data['logicalCPUCoresPerPackage'] + @physical_cpu_cores_per_package = data.dig('physicalCPUCoresPerPackage', '_value').to_i if data['physicalCPUCoresPerPackage'] + @logical_cpu_cores_per_package = data.dig('logicalCPUCoresPerPackage', '_value').to_i if data['logicalCPUCoresPerPackage'] @platform_record = Kernel.const_get("XCResult::Models::#{data.dig('platformRecord', '_type', '_name')}").new(data.dig('platformRecord')) end end @@ -224,14 +224,14 @@ class ActionRunDestinationRecord # @return [ActionDeviceRecord] localComputerRecord attr_reader :local_computer_record # @return [ActionSDKRecord] targetSDKRecord - attr_reader :target_sdkrecord + attr_reader :target_sdk_record def initialize(data) @display_name = data.dig('displayName', '_value') @target_architecture = data.dig('targetArchitecture', '_value') @target_device_record = Kernel.const_get("XCResult::Models::#{data.dig('targetDeviceRecord', '_type', '_name')}").new(data.dig('targetDeviceRecord')) @local_computer_record = Kernel.const_get("XCResult::Models::#{data.dig('localComputerRecord', '_type', '_name')}").new(data.dig('localComputerRecord')) - @target_sdkrecord = Kernel.const_get("XCResult::Models::#{data.dig('targetSDKRecord', '_type', '_name')}").new(data.dig('targetSDKRecord')) + @target_sdk_record = Kernel.const_get("XCResult::Models::#{data.dig('targetSDKRecord', '_type', '_name')}").new(data.dig('targetSDKRecord')) end end From 6bec119553bfc93eea30afe5609f65870f7401ad Mon Sep 17 00:00:00 2001 From: ainame Date: Wed, 21 Jul 2021 18:23:06 +0900 Subject: [PATCH 12/24] Fix rubocop issues --- lib/xcresult/model_generator.rb | 68 +++++++++++++++++---------------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/lib/xcresult/model_generator.rb b/lib/xcresult/model_generator.rb index 573aa9c..d732617 100644 --- a/lib/xcresult/model_generator.rb +++ b/lib/xcresult/model_generator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'json' require 'erb' require 'tsort' @@ -24,21 +26,21 @@ def initialize(format) def write(destination) file = File.open(destination, 'w+') file.puts(<<~"HEADER") - # This is a generated file. Don't modify this directly! - # Last generated at: #{Time.now.utc} - # - # #{@format.name} - # #{@format.version} - # #{@format.signature} - require 'time' + # This is a generated file. Don't modify this directly! + # Last generated at: #{Time.now.utc} + # + # #{@format.name} + # #{@format.version} + # #{@format.signature} + require 'time' HEADER - file.puts(<<~TEMPLATE) - module XCResult - module Models + file.puts(<<~OPEN_MODULE) + module XCResult + module Models - TEMPLATE + OPEN_MODULE sorted_types.each do |type| type_text = compose_type(type, 2 * 2) @@ -46,10 +48,10 @@ module Models file.puts('') end - file.puts(<<~TEMPLATE) + file.puts(<<~CLOSE_MODULE) + end end - end - TEMPLATE + CLOSE_MODULE ensure file.close end @@ -64,26 +66,26 @@ def sorted_types def compose_type(type, indentation) type_def = <<~"TYPE" - <%= type.raw_text.each_line.map { |line| '# ' + line[2..-1] }.join %> - <% if type.supertype %> - class <%= type.name %> < <%= type.supertype %> - <% else %> - class <%= type.name %> - <% end%> - <% type.properties.each do |property| %> - <%= property.rdoc_comment %> - attr_reader :<%= property.name_in_snake_case %> - <% end %> - - def initialize(data) - <% type.properties.each do |property| %> - @<%= property.name_in_snake_case %> = <%= property.mapping(type_to_kind[property.main_type], 'data') %> - <% end %> - <% if type.supertype %> - super - <% end %> + <%= type.raw_text.each_line.map { |line| '# ' + line[2..-1] }.join %> + <% if type.supertype %> + class <%= type.name %> < <%= type.supertype %> + <% else %> + class <%= type.name %> + <% end%> + <% type.properties.each do |property| %> + <%= property.rdoc_comment %> + attr_reader :<%= property.name_in_snake_case %> + <% end %> + + def initialize(data) + <% type.properties.each do |property| %> + @<%= property.name_in_snake_case %> = <%= property.mapping(type_to_kind[property.main_type], 'data') %> + <% end %> + <% if type.supertype %> + super + <% end %> + end end - end TYPE type_def = ERB.new(type_def, trim_mode: '<>').result_with_hash(type: type, type_to_kind: @type_to_kind) type_def.each_line.map { |line| "#{' ' * indentation}#{line}" }.join From 2eb2c705931168aa9dd9880686193ca30704a92f Mon Sep 17 00:00:00 2001 From: ainame Date: Thu, 22 Jul 2021 16:27:46 +0900 Subject: [PATCH 13/24] Extract Models.load_class --- lib/xcresult/model_generator.rb | 6 +- lib/xcresult/models.gen.rb | 142 ++++++++++++++++---------------- 2 files changed, 78 insertions(+), 70 deletions(-) diff --git a/lib/xcresult/model_generator.rb b/lib/xcresult/model_generator.rb index d732617..efe5133 100644 --- a/lib/xcresult/model_generator.rb +++ b/lib/xcresult/model_generator.rb @@ -40,6 +40,10 @@ def write(destination) module XCResult module Models + def self.load_class(class_name) + Kernel.const_get("XCResult::Models::\#{class_name}") + end + OPEN_MODULE sorted_types.each do |type| @@ -176,7 +180,7 @@ def _mapping(kind, variable_name) if kind == 'object' type_access_key = array ? "'_type', '_name'" : "'#{name}', '_type', '_name'" value_access_key = array ? variable_name : "#{variable_name}.dig('#{name}')" - "Kernel.const_get(\"XCResult::Models::\#{#{variable_name}.dig(#{type_access_key})}\").new(#{value_access_key})" + "Models.load_class(#{variable_name}.dig(#{type_access_key})).new(#{value_access_key})" elsif kind == 'value' && main_type == 'Date' "Time.parse(#{variable_name}.dig('#{name}', '_value'))" elsif kind == 'value' && main_type == 'Int' diff --git a/lib/xcresult/models.gen.rb b/lib/xcresult/models.gen.rb index 17d5e85..2fa18e1 100644 --- a/lib/xcresult/models.gen.rb +++ b/lib/xcresult/models.gen.rb @@ -1,5 +1,5 @@ # This is a generated file. Don't modify this directly! -# Last generated at: 2021-07-21 01:01:36 UTC +# Last generated at: 2021-07-22 07:27:14 UTC # # Name: Xcode Result Types # Version: 3.30 @@ -9,6 +9,10 @@ module XCResult module Models + def self.load_class(class_name) + Kernel.const_get("XCResult::Models::#{class_name}") + end + # - ActionAbstractTestSummary # * Kind: object # * Properties: @@ -99,7 +103,7 @@ def initialize(data) @ram_size_in_megabytes = data.dig('ramSizeInMegabytes', '_value').to_i if data['ramSizeInMegabytes'] @physical_cpu_cores_per_package = data.dig('physicalCPUCoresPerPackage', '_value').to_i if data['physicalCPUCoresPerPackage'] @logical_cpu_cores_per_package = data.dig('logicalCPUCoresPerPackage', '_value').to_i if data['logicalCPUCoresPerPackage'] - @platform_record = Kernel.const_get("XCResult::Models::#{data.dig('platformRecord', '_type', '_name')}").new(data.dig('platformRecord')) + @platform_record = Models.load_class(data.dig('platformRecord', '_type', '_name')).new(data.dig('platformRecord')) end end @@ -155,9 +159,9 @@ def initialize(data) @title = data.dig('title', '_value') if data['title'] @started_time = Time.parse(data.dig('startedTime', '_value')) @ended_time = Time.parse(data.dig('endedTime', '_value')) - @run_destination = Kernel.const_get("XCResult::Models::#{data.dig('runDestination', '_type', '_name')}").new(data.dig('runDestination')) - @build_result = Kernel.const_get("XCResult::Models::#{data.dig('buildResult', '_type', '_name')}").new(data.dig('buildResult')) - @action_result = Kernel.const_get("XCResult::Models::#{data.dig('actionResult', '_type', '_name')}").new(data.dig('actionResult')) + @run_destination = Models.load_class(data.dig('runDestination', '_type', '_name')).new(data.dig('runDestination')) + @build_result = Models.load_class(data.dig('buildResult', '_type', '_name')).new(data.dig('buildResult')) + @action_result = Models.load_class(data.dig('actionResult', '_type', '_name')).new(data.dig('actionResult')) end end @@ -196,13 +200,13 @@ class ActionResult def initialize(data) @result_name = data.dig('resultName', '_value') @status = data.dig('status', '_value') - @metrics = Kernel.const_get("XCResult::Models::#{data.dig('metrics', '_type', '_name')}").new(data.dig('metrics')) - @issues = Kernel.const_get("XCResult::Models::#{data.dig('issues', '_type', '_name')}").new(data.dig('issues')) - @coverage = Kernel.const_get("XCResult::Models::#{data.dig('coverage', '_type', '_name')}").new(data.dig('coverage')) - @timeline_ref = Kernel.const_get("XCResult::Models::#{data.dig('timelineRef', '_type', '_name')}").new(data.dig('timelineRef')) if data['timelineRef'] - @log_ref = Kernel.const_get("XCResult::Models::#{data.dig('logRef', '_type', '_name')}").new(data.dig('logRef')) if data['logRef'] - @tests_ref = Kernel.const_get("XCResult::Models::#{data.dig('testsRef', '_type', '_name')}").new(data.dig('testsRef')) if data['testsRef'] - @diagnostics_ref = Kernel.const_get("XCResult::Models::#{data.dig('diagnosticsRef', '_type', '_name')}").new(data.dig('diagnosticsRef')) if data['diagnosticsRef'] + @metrics = Models.load_class(data.dig('metrics', '_type', '_name')).new(data.dig('metrics')) + @issues = Models.load_class(data.dig('issues', '_type', '_name')).new(data.dig('issues')) + @coverage = Models.load_class(data.dig('coverage', '_type', '_name')).new(data.dig('coverage')) + @timeline_ref = Models.load_class(data.dig('timelineRef', '_type', '_name')).new(data.dig('timelineRef')) if data['timelineRef'] + @log_ref = Models.load_class(data.dig('logRef', '_type', '_name')).new(data.dig('logRef')) if data['logRef'] + @tests_ref = Models.load_class(data.dig('testsRef', '_type', '_name')).new(data.dig('testsRef')) if data['testsRef'] + @diagnostics_ref = Models.load_class(data.dig('diagnosticsRef', '_type', '_name')).new(data.dig('diagnosticsRef')) if data['diagnosticsRef'] end end @@ -229,9 +233,9 @@ class ActionRunDestinationRecord def initialize(data) @display_name = data.dig('displayName', '_value') @target_architecture = data.dig('targetArchitecture', '_value') - @target_device_record = Kernel.const_get("XCResult::Models::#{data.dig('targetDeviceRecord', '_type', '_name')}").new(data.dig('targetDeviceRecord')) - @local_computer_record = Kernel.const_get("XCResult::Models::#{data.dig('localComputerRecord', '_type', '_name')}").new(data.dig('localComputerRecord')) - @target_sdk_record = Kernel.const_get("XCResult::Models::#{data.dig('targetSDKRecord', '_type', '_name')}").new(data.dig('targetSDKRecord')) + @target_device_record = Models.load_class(data.dig('targetDeviceRecord', '_type', '_name')).new(data.dig('targetDeviceRecord')) + @local_computer_record = Models.load_class(data.dig('localComputerRecord', '_type', '_name')).new(data.dig('localComputerRecord')) + @target_sdk_record = Models.load_class(data.dig('targetSDKRecord', '_type', '_name')).new(data.dig('targetSDKRecord')) end end @@ -298,8 +302,8 @@ def initialize(data) @uuid = data.dig('uuid', '_value') @start = Time.parse(data.dig('start', '_value')) if data['start'] @finish = Time.parse(data.dig('finish', '_value')) if data['finish'] - @attachments = (data.dig('attachments', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } - @subactivities = (data.dig('subactivities', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + @attachments = (data.dig('attachments', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } + @subactivities = (data.dig('subactivities', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } @failure_summary_ids = (data.dig('failureSummaryIDs', '_values') || []).map {|d| d.dig('failureSummaryIDs', '_value') } @expected_failure_ids = (data.dig('expectedFailureIDs', '_values') || []).map {|d| d.dig('expectedFailureIDs', '_value') } end @@ -341,11 +345,11 @@ def initialize(data) @uniform_type_identifier = data.dig('uniformTypeIdentifier', '_value') @name = data.dig('name', '_value') if data['name'] @timestamp = Time.parse(data.dig('timestamp', '_value')) if data['timestamp'] - @user_info = Kernel.const_get("XCResult::Models::#{data.dig('userInfo', '_type', '_name')}").new(data.dig('userInfo')) if data['userInfo'] + @user_info = Models.load_class(data.dig('userInfo', '_type', '_name')).new(data.dig('userInfo')) if data['userInfo'] @lifetime = data.dig('lifetime', '_value') @in_activity_identifier = data.dig('inActivityIdentifier', '_value').to_i @filename = data.dig('filename', '_value') if data['filename'] - @payload_ref = Kernel.const_get("XCResult::Models::#{data.dig('payloadRef', '_type', '_name')}").new(data.dig('payloadRef')) if data['payloadRef'] + @payload_ref = Models.load_class(data.dig('payloadRef', '_type', '_name')).new(data.dig('payloadRef')) if data['payloadRef'] @payload_size = data.dig('payloadSize', '_value').to_i end end @@ -370,7 +374,7 @@ class ActionTestExpectedFailure def initialize(data) @uuid = data.dig('uuid', '_value') @failure_reason = data.dig('failureReason', '_value') if data['failureReason'] - @failure_summary = Kernel.const_get("XCResult::Models::#{data.dig('failureSummary', '_type', '_name')}").new(data.dig('failureSummary')) if data['failureSummary'] + @failure_summary = Models.load_class(data.dig('failureSummary', '_type', '_name')).new(data.dig('failureSummary')) if data['failureSummary'] @is_top_level_failure = data.dig('isTopLevelFailure', '_value') end end @@ -424,9 +428,9 @@ def initialize(data) @uuid = data.dig('uuid', '_value') @issue_type = data.dig('issueType', '_value') if data['issueType'] @detailed_description = data.dig('detailedDescription', '_value') if data['detailedDescription'] - @attachments = (data.dig('attachments', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } - @associated_error = Kernel.const_get("XCResult::Models::#{data.dig('associatedError', '_type', '_name')}").new(data.dig('associatedError')) if data['associatedError'] - @source_code_context = Kernel.const_get("XCResult::Models::#{data.dig('sourceCodeContext', '_type', '_name')}").new(data.dig('sourceCodeContext')) if data['sourceCodeContext'] + @attachments = (data.dig('attachments', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } + @associated_error = Models.load_class(data.dig('associatedError', '_type', '_name')).new(data.dig('associatedError')) if data['associatedError'] + @source_code_context = Models.load_class(data.dig('sourceCodeContext', '_type', '_name')).new(data.dig('sourceCodeContext')) if data['sourceCodeContext'] @timestamp = Time.parse(data.dig('timestamp', '_value')) if data['timestamp'] @is_top_level_failure = data.dig('isTopLevelFailure', '_value') end @@ -474,7 +478,7 @@ class ActionTestMetadata < ActionTestSummaryIdentifiableObject def initialize(data) @test_status = data.dig('testStatus', '_value') @duration = data.dig('duration', '_value').to_f if data['duration'] - @summary_ref = Kernel.const_get("XCResult::Models::#{data.dig('summaryRef', '_type', '_name')}").new(data.dig('summaryRef')) if data['summaryRef'] + @summary_ref = Models.load_class(data.dig('summaryRef', '_type', '_name')).new(data.dig('summaryRef')) if data['summaryRef'] @performance_metrics_count = data.dig('performanceMetricsCount', '_value').to_i @failure_summaries_count = data.dig('failureSummariesCount', '_value').to_i @activity_summaries_count = data.dig('activitySummariesCount', '_value').to_i @@ -565,7 +569,7 @@ class ActionTestPlanRunSummaries attr_reader :summaries def initialize(data) - @summaries = (data.dig('summaries', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + @summaries = (data.dig('summaries', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } end end @@ -579,7 +583,7 @@ class ActionTestPlanRunSummary < ActionAbstractTestSummary attr_reader :testable_summaries def initialize(data) - @testable_summaries = (data.dig('testableSummaries', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + @testable_summaries = (data.dig('testableSummaries', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } super end end @@ -638,12 +642,12 @@ class ActionTestSummary < ActionTestSummaryIdentifiableObject def initialize(data) @test_status = data.dig('testStatus', '_value') @duration = data.dig('duration', '_value').to_f - @performance_metrics = (data.dig('performanceMetrics', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } - @failure_summaries = (data.dig('failureSummaries', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } - @expected_failures = (data.dig('expectedFailures', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } - @skip_notice_summary = Kernel.const_get("XCResult::Models::#{data.dig('skipNoticeSummary', '_type', '_name')}").new(data.dig('skipNoticeSummary')) if data['skipNoticeSummary'] - @activity_summaries = (data.dig('activitySummaries', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } - @repetition_policy_summary = Kernel.const_get("XCResult::Models::#{data.dig('repetitionPolicySummary', '_type', '_name')}").new(data.dig('repetitionPolicySummary')) if data['repetitionPolicySummary'] + @performance_metrics = (data.dig('performanceMetrics', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } + @failure_summaries = (data.dig('failureSummaries', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } + @expected_failures = (data.dig('expectedFailures', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } + @skip_notice_summary = Models.load_class(data.dig('skipNoticeSummary', '_type', '_name')).new(data.dig('skipNoticeSummary')) if data['skipNoticeSummary'] + @activity_summaries = (data.dig('activitySummaries', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } + @repetition_policy_summary = Models.load_class(data.dig('repetitionPolicySummary', '_type', '_name')).new(data.dig('repetitionPolicySummary')) if data['repetitionPolicySummary'] super end end @@ -662,7 +666,7 @@ class ActionTestSummaryGroup < ActionTestSummaryIdentifiableObject def initialize(data) @duration = data.dig('duration', '_value').to_f - @subtests = (data.dig('subtests', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + @subtests = (data.dig('subtests', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } super end end @@ -701,9 +705,9 @@ def initialize(data) @project_relative_path = data.dig('projectRelativePath', '_value') if data['projectRelativePath'] @target_name = data.dig('targetName', '_value') if data['targetName'] @test_kind = data.dig('testKind', '_value') if data['testKind'] - @tests = (data.dig('tests', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + @tests = (data.dig('tests', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } @diagnostics_directory_name = data.dig('diagnosticsDirectoryName', '_value') if data['diagnosticsDirectoryName'] - @failure_summaries = (data.dig('failureSummaries', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + @failure_summaries = (data.dig('failureSummaries', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } @test_language = data.dig('testLanguage', '_value') if data['testLanguage'] @test_region = data.dig('testRegion', '_value') if data['testRegion'] super @@ -727,7 +731,7 @@ class ActionsInvocationMetadata def initialize(data) @creating_workspace_file_path = data.dig('creatingWorkspaceFilePath', '_value') @unique_identifier = data.dig('uniqueIdentifier', '_value') - @scheme_identifier = Kernel.const_get("XCResult::Models::#{data.dig('schemeIdentifier', '_type', '_name')}").new(data.dig('schemeIdentifier')) if data['schemeIdentifier'] + @scheme_identifier = Models.load_class(data.dig('schemeIdentifier', '_type', '_name')).new(data.dig('schemeIdentifier')) if data['schemeIdentifier'] end end @@ -752,11 +756,11 @@ class ActionsInvocationRecord attr_reader :archive def initialize(data) - @metadata_ref = Kernel.const_get("XCResult::Models::#{data.dig('metadataRef', '_type', '_name')}").new(data.dig('metadataRef')) if data['metadataRef'] - @metrics = Kernel.const_get("XCResult::Models::#{data.dig('metrics', '_type', '_name')}").new(data.dig('metrics')) - @issues = Kernel.const_get("XCResult::Models::#{data.dig('issues', '_type', '_name')}").new(data.dig('issues')) - @actions = (data.dig('actions', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } - @archive = Kernel.const_get("XCResult::Models::#{data.dig('archive', '_type', '_name')}").new(data.dig('archive')) if data['archive'] + @metadata_ref = Models.load_class(data.dig('metadataRef', '_type', '_name')).new(data.dig('metadataRef')) if data['metadataRef'] + @metrics = Models.load_class(data.dig('metrics', '_type', '_name')).new(data.dig('metrics')) + @issues = Models.load_class(data.dig('issues', '_type', '_name')).new(data.dig('issues')) + @actions = (data.dig('actions', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } + @archive = Models.load_class(data.dig('archive', '_type', '_name')).new(data.dig('archive')) if data['archive'] end end @@ -793,9 +797,9 @@ class ActivityLogAnalyzerControlFlowStep < ActivityLogAnalyzerStep def initialize(data) @title = data.dig('title', '_value') - @start_location = Kernel.const_get("XCResult::Models::#{data.dig('startLocation', '_type', '_name')}").new(data.dig('startLocation')) if data['startLocation'] - @end_location = Kernel.const_get("XCResult::Models::#{data.dig('endLocation', '_type', '_name')}").new(data.dig('endLocation')) if data['endLocation'] - @edges = (data.dig('edges', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + @start_location = Models.load_class(data.dig('startLocation', '_type', '_name')).new(data.dig('startLocation')) if data['startLocation'] + @end_location = Models.load_class(data.dig('endLocation', '_type', '_name')).new(data.dig('endLocation')) if data['endLocation'] + @edges = (data.dig('edges', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } super end end @@ -812,8 +816,8 @@ class ActivityLogAnalyzerControlFlowStepEdge attr_reader :end_location def initialize(data) - @start_location = Kernel.const_get("XCResult::Models::#{data.dig('startLocation', '_type', '_name')}").new(data.dig('startLocation')) if data['startLocation'] - @end_location = Kernel.const_get("XCResult::Models::#{data.dig('endLocation', '_type', '_name')}").new(data.dig('endLocation')) if data['endLocation'] + @start_location = Models.load_class(data.dig('startLocation', '_type', '_name')).new(data.dig('startLocation')) if data['startLocation'] + @end_location = Models.load_class(data.dig('endLocation', '_type', '_name')).new(data.dig('endLocation')) if data['endLocation'] end end @@ -837,7 +841,7 @@ class ActivityLogAnalyzerEventStep < ActivityLogAnalyzerStep def initialize(data) @title = data.dig('title', '_value') - @location = Kernel.const_get("XCResult::Models::#{data.dig('location', '_type', '_name')}").new(data.dig('location')) if data['location'] + @location = Models.load_class(data.dig('location', '_type', '_name')).new(data.dig('location')) if data['location'] @description = data.dig('description', '_value') @call_depth = data.dig('callDepth', '_value').to_i super @@ -872,8 +876,8 @@ def initialize(data) @title = data.dig('title', '_value') @short_title = data.dig('shortTitle', '_value') if data['shortTitle'] @category = data.dig('category', '_value') if data['category'] - @location = Kernel.const_get("XCResult::Models::#{data.dig('location', '_type', '_name')}").new(data.dig('location')) if data['location'] - @annotations = (data.dig('annotations', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + @location = Models.load_class(data.dig('location', '_type', '_name')).new(data.dig('location')) if data['location'] + @annotations = (data.dig('annotations', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } end end @@ -893,7 +897,7 @@ class ActivityLogAnalyzerResultMessage < ActivityLogMessage attr_reader :key_event_index def initialize(data) - @steps = (data.dig('steps', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + @steps = (data.dig('steps', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } @result_type = data.dig('resultType', '_value') if data['resultType'] @key_event_index = data.dig('keyEventIndex', '_value').to_i super @@ -945,9 +949,9 @@ def initialize(data) @start_time = Time.parse(data.dig('startTime', '_value')) if data['startTime'] @duration = data.dig('duration', '_value').to_f @result = data.dig('result', '_value') if data['result'] - @location = Kernel.const_get("XCResult::Models::#{data.dig('location', '_type', '_name')}").new(data.dig('location')) if data['location'] - @subsections = (data.dig('subsections', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } - @messages = (data.dig('messages', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + @location = Models.load_class(data.dig('location', '_type', '_name')).new(data.dig('location')) if data['location'] + @subsections = (data.dig('subsections', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } + @messages = (data.dig('messages', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } end end @@ -1002,7 +1006,7 @@ class ActivityLogMessageAnnotation def initialize(data) @title = data.dig('title', '_value') - @location = Kernel.const_get("XCResult::Models::#{data.dig('location', '_type', '_name')}").new(data.dig('location')) if data['location'] + @location = Models.load_class(data.dig('location', '_type', '_name')).new(data.dig('location')) if data['location'] end end @@ -1113,8 +1117,8 @@ class CodeCoverageInfo def initialize(data) @has_coverage_data = data.dig('hasCoverageData', '_value') - @report_ref = Kernel.const_get("XCResult::Models::#{data.dig('reportRef', '_type', '_name')}").new(data.dig('reportRef')) if data['reportRef'] - @archive_ref = Kernel.const_get("XCResult::Models::#{data.dig('archiveRef', '_type', '_name')}").new(data.dig('archiveRef')) if data['archiveRef'] + @report_ref = Models.load_class(data.dig('reportRef', '_type', '_name')).new(data.dig('reportRef')) if data['reportRef'] + @archive_ref = Models.load_class(data.dig('archiveRef', '_type', '_name')).new(data.dig('archiveRef')) if data['archiveRef'] end end @@ -1205,7 +1209,7 @@ def initialize(data) @issue_type = data.dig('issueType', '_value') @message = data.dig('message', '_value') @producing_target = data.dig('producingTarget', '_value') if data['producingTarget'] - @document_location_in_creating_workspace = Kernel.const_get("XCResult::Models::#{data.dig('documentLocationInCreatingWorkspace', '_type', '_name')}").new(data.dig('documentLocationInCreatingWorkspace')) if data['documentLocationInCreatingWorkspace'] + @document_location_in_creating_workspace = Models.load_class(data.dig('documentLocationInCreatingWorkspace', '_type', '_name')).new(data.dig('documentLocationInCreatingWorkspace')) if data['documentLocationInCreatingWorkspace'] end end @@ -1235,7 +1239,7 @@ class Reference def initialize(data) @id = data.dig('id', '_value') - @target_type = Kernel.const_get("XCResult::Models::#{data.dig('targetType', '_type', '_name')}").new(data.dig('targetType')) if data['targetType'] + @target_type = Models.load_class(data.dig('targetType', '_type', '_name')).new(data.dig('targetType')) if data['targetType'] end end @@ -1257,10 +1261,10 @@ class ResultIssueSummaries attr_reader :warning_summaries def initialize(data) - @analyzer_warning_summaries = (data.dig('analyzerWarningSummaries', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } - @error_summaries = (data.dig('errorSummaries', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } - @test_failure_summaries = (data.dig('testFailureSummaries', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } - @warning_summaries = (data.dig('warningSummaries', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + @analyzer_warning_summaries = (data.dig('analyzerWarningSummaries', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } + @error_summaries = (data.dig('errorSummaries', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } + @test_failure_summaries = (data.dig('testFailureSummaries', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } + @warning_summaries = (data.dig('warningSummaries', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } end end @@ -1306,7 +1310,7 @@ class SortedKeyValueArray attr_reader :storage def initialize(data) - @storage = (data.dig('storage', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + @storage = (data.dig('storage', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } end end @@ -1339,8 +1343,8 @@ class SourceCodeContext attr_reader :call_stack def initialize(data) - @location = Kernel.const_get("XCResult::Models::#{data.dig('location', '_type', '_name')}").new(data.dig('location')) if data['location'] - @call_stack = (data.dig('callStack', '_values') || []).map {|d| Kernel.const_get("XCResult::Models::#{d.dig('_type', '_name')}").new(d) } + @location = Models.load_class(data.dig('location', '_type', '_name')).new(data.dig('location')) if data['location'] + @call_stack = (data.dig('callStack', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } end end @@ -1357,7 +1361,7 @@ class SourceCodeFrame def initialize(data) @address_string = data.dig('addressString', '_value') if data['addressString'] - @symbol_info = Kernel.const_get("XCResult::Models::#{data.dig('symbolInfo', '_type', '_name')}").new(data.dig('symbolInfo')) if data['symbolInfo'] + @symbol_info = Models.load_class(data.dig('symbolInfo', '_type', '_name')).new(data.dig('symbolInfo')) if data['symbolInfo'] end end @@ -1395,7 +1399,7 @@ class SourceCodeSymbolInfo def initialize(data) @image_name = data.dig('imageName', '_value') if data['imageName'] @symbol_name = data.dig('symbolName', '_value') if data['symbolName'] - @location = Kernel.const_get("XCResult::Models::#{data.dig('location', '_type', '_name')}").new(data.dig('location')) if data['location'] + @location = Models.load_class(data.dig('location', '_type', '_name')).new(data.dig('location')) if data['location'] end end @@ -1424,7 +1428,7 @@ class TestAssociatedError def initialize(data) @domain = data.dig('domain', '_value') if data['domain'] @code = data.dig('code', '_value').to_i if data['code'] - @user_info = Kernel.const_get("XCResult::Models::#{data.dig('userInfo', '_type', '_name')}").new(data.dig('userInfo')) if data['userInfo'] + @user_info = Models.load_class(data.dig('userInfo', '_type', '_name')).new(data.dig('userInfo')) if data['userInfo'] end end @@ -1456,7 +1460,7 @@ class TypeDefinition def initialize(data) @name = data.dig('name', '_value') - @supertype = Kernel.const_get("XCResult::Models::#{data.dig('supertype', '_type', '_name')}").new(data.dig('supertype')) if data['supertype'] + @supertype = Models.load_class(data.dig('supertype', '_type', '_name')).new(data.dig('supertype')) if data['supertype'] end end From 893edf8ff3b13191d4bc14ba34ef33b9fdfea113 Mon Sep 17 00:00:00 2001 From: ainame Date: Mon, 26 Jul 2021 01:50:14 +0900 Subject: [PATCH 14/24] Move XCResult::Models.load_class to models.rb --- lib/xcresult/model_generator.rb | 4 ---- lib/xcresult/models.gen.rb | 6 +----- lib/xcresult/models.rb | 4 ++++ 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/lib/xcresult/model_generator.rb b/lib/xcresult/model_generator.rb index efe5133..29a4f72 100644 --- a/lib/xcresult/model_generator.rb +++ b/lib/xcresult/model_generator.rb @@ -40,10 +40,6 @@ def write(destination) module XCResult module Models - def self.load_class(class_name) - Kernel.const_get("XCResult::Models::\#{class_name}") - end - OPEN_MODULE sorted_types.each do |type| diff --git a/lib/xcresult/models.gen.rb b/lib/xcresult/models.gen.rb index 2fa18e1..66d0482 100644 --- a/lib/xcresult/models.gen.rb +++ b/lib/xcresult/models.gen.rb @@ -1,5 +1,5 @@ # This is a generated file. Don't modify this directly! -# Last generated at: 2021-07-22 07:27:14 UTC +# Last generated at: 2021-07-25 16:50:48 UTC # # Name: Xcode Result Types # Version: 3.30 @@ -9,10 +9,6 @@ module XCResult module Models - def self.load_class(class_name) - Kernel.const_get("XCResult::Models::#{class_name}") - end - # - ActionAbstractTestSummary # * Kind: object # * Properties: diff --git a/lib/xcresult/models.rb b/lib/xcresult/models.rb index 106f03c..db57d2d 100644 --- a/lib/xcresult/models.rb +++ b/lib/xcresult/models.rb @@ -5,6 +5,10 @@ # This file is meant to define extensions to generated models in models.gen.rb. module XCResult module Models + def self.load_class(class_name) + Kernel.const_get("XCResult::Models::#{class_name}") + end + class ActionTestableSummary def all_tests tests.map(&:all_subtests).flatten From f065120c9566a3ee6c97bc1fec929656c2b0f0d3 Mon Sep 17 00:00:00 2001 From: ainame Date: Wed, 24 Nov 2021 04:46:25 +0000 Subject: [PATCH 15/24] Support JSON foramt parsing --- lib/xcresult/model_generator.rb | 113 ++++++++++++-------------------- 1 file changed, 42 insertions(+), 71 deletions(-) diff --git a/lib/xcresult/model_generator.rb b/lib/xcresult/model_generator.rb index 29a4f72..9ead46e 100644 --- a/lib/xcresult/model_generator.rb +++ b/lib/xcresult/model_generator.rb @@ -7,7 +7,7 @@ module XCResult class ModelGenerator def self.format_description - `xcrun xcresulttool formatDescription get` + `xcrun xcresulttool formatDescription get --format json` end def self.generate(destination) @@ -29,9 +29,9 @@ def write(destination) # This is a generated file. Don't modify this directly! # Last generated at: #{Time.now.utc} # - # #{@format.name} - # #{@format.version} - # #{@format.signature} + # Name: #{@format.name} + # Version: #{@format.version} + # Signature: #{@format.signature} require 'time' HEADER @@ -66,7 +66,6 @@ def sorted_types def compose_type(type, indentation) type_def = <<~"TYPE" - <%= type.raw_text.each_line.map { |line| '# ' + line[2..-1] }.join %> <% if type.supertype %> class <%= type.name %> < <%= type.supertype %> <% else %> @@ -79,7 +78,7 @@ class <%= type.name %> def initialize(data) <% type.properties.each do |property| %> - @<%= property.name_in_snake_case %> = <%= property.mapping(type_to_kind[property.main_type], 'data') %> + @<%= property.name_in_snake_case %> = <%= property.mapping(type_to_kind[property.wrapped_type || property.type], 'data') %> <% end %> <% if type.supertype %> super @@ -97,68 +96,40 @@ class Parser def initialize(format:) @raw_format = format - @lines = format.each_line.to_a + @json = JSON.parse(format) @format = Format.new end def parse - @format.name = @lines.shift.chomp - @format.version = @lines.shift.chomp - @format.signature = @lines.shift.chomp - - # Drop "- Types" lines - @lines.shift - - # Parsing Types - types = [] - type = nil - - until @lines.empty? - line = @lines.shift - case line - when /\s*-\s+(.*)$/ - types << type if type - type = Type.new(name: Regexp.last_match(1), raw_text: line, properties: []) - when /\s*\*\s*Supertype: (.*)$/ - type.supertype = Regexp.last_match(1) - type.raw_text << line - when /\s*\*\s*Kind: (.*)$/ - type.kind = Regexp.last_match(1) - type.raw_text << line - when /\s*\*\s*Properties:$/ - type.raw_text << line - parse_properties(type) - end - end - - types << type if type - @format.types = types - end - - def parse_properties(type) - while @lines.first =~ /\s*\+\s(?.*):\s*(?.*)$/ - optional = false - array = false - parsed_name = Regexp.last_match[:name] - parsed_type = Regexp.last_match[:type] - main_type = parsed_type - - if parsed_type =~ /^\[(.*)\]$/ - array = true - main_type = Regexp.last_match[1] - elsif parsed_type =~ /^(.*)\?$/ - optional = true - main_type = Regexp.last_match[1] - end - type.properties << Property.new(name: parsed_name, type: parsed_type, main_type: main_type, optional: optional, array: array) - type.raw_text << @lines.shift - end + @format.name = @json['name'] + @format.version = @json['version'].values.join('.') + @format.signature = @json['signature'] + @format.types = @json['types'].map do |type| + # We use Ruby native classes for values, like Date, String, Int, Double, etc.. + next if type['kind'] == 'value' + + Type.new( + name: type.dig('type', 'name'), + supertype: type.dig('type', 'supertype'), + kind: type['kind'], + raw_text: JSON.pretty_generate(type), + properties: type.fetch('properties', []).map do |prop| + Property.new( + name: prop['name'], + type: prop['type'], + wrapped_type: prop['wrappedType'], + is_optional: prop['isOptional'], + is_internal: prop['isInternal'] + ) + end + ) + end.compact end end Format = Struct.new(:name, :version, :signature, :types, keyword_init: true) Type = Struct.new(:name, :supertype, :kind, :properties, :raw_text, keyword_init: true) - Property = Struct.new(:name, :type, :main_type, :optional, :array, keyword_init: true) do + Property = Struct.new(:name, :type, :wrapped_type, :is_optional, :is_internal, keyword_init: true) do def name_in_snake_case name .gsub(/(CPU|ID|MHz|UTI|SDK)/) { Regexp.last_match[1].downcase.capitalize } # normalize acronyms @@ -167,21 +138,21 @@ def name_in_snake_case end def mapping(kind, variable_name) - return "(#{variable_name}.dig('#{name}', '_values') || []).map {|d| #{_mapping(kind, 'd')} }" if array + return "(#{variable_name}.dig('#{name}', '_values') || []).map {|d| #{_mapping(kind, 'd')} }" if type == 'Array' - _mapping(kind, variable_name) + (optional ? " if #{variable_name}['#{name}']" : '') + _mapping(kind, variable_name) + (is_optional ? " if #{variable_name}['#{name}']" : '') end def _mapping(kind, variable_name) if kind == 'object' - type_access_key = array ? "'_type', '_name'" : "'#{name}', '_type', '_name'" - value_access_key = array ? variable_name : "#{variable_name}.dig('#{name}')" + type_access_key = type == 'Array' ? "'_type', '_name'" : "'#{name}', '_type', '_name'" + value_access_key = type == 'Array' ? variable_name : "#{variable_name}.dig('#{name}')" "Models.load_class(#{variable_name}.dig(#{type_access_key})).new(#{value_access_key})" - elsif kind == 'value' && main_type == 'Date' + elsif kind == 'value' && [type, wrapped_type].include?('Date') "Time.parse(#{variable_name}.dig('#{name}', '_value'))" - elsif kind == 'value' && main_type == 'Int' + elsif kind == 'value' && [type, wrapped_type].include?('Int') "#{variable_name}.dig('#{name}', '_value').to_i" - elsif kind == 'value' && main_type == 'Double' + elsif kind == 'value' && [type, wrapped_type].include?('Double') "#{variable_name}.dig('#{name}', '_value').to_f" else "#{variable_name}.dig('#{name}', '_value')" @@ -189,12 +160,12 @@ def _mapping(kind, variable_name) end def rdoc_comment - if array - "# @return [Array<#{main_type}>] #{name}" - elsif optional - "# @return [#{main_type}, nil] #{name}" + if type == 'Array' + "# @return [Array<#{wrapped_type}>] #{name_in_snake_case}" + elsif is_optional + "# @return [#{wrapped_type}, nil] #{name_in_snake_case}" else - "# @return [#{main_type}] #{name}" + "# @return [#{type}] #{name_in_snake_case}" end end end From 4da709c9033a31de5ac980d587d0afdaf10ea337 Mon Sep 17 00:00:00 2001 From: ainame Date: Wed, 24 Nov 2021 04:47:47 +0000 Subject: [PATCH 16/24] Update models --- lib/xcresult/models.gen.rb | 839 +++++++++---------------------------- 1 file changed, 207 insertions(+), 632 deletions(-) diff --git a/lib/xcresult/models.gen.rb b/lib/xcresult/models.gen.rb index 66d0482..70b4eeb 100644 --- a/lib/xcresult/models.gen.rb +++ b/lib/xcresult/models.gen.rb @@ -1,18 +1,14 @@ # This is a generated file. Don't modify this directly! -# Last generated at: 2021-07-25 16:50:48 UTC +# Last generated at: 2021-11-24 04:47:30 UTC # # Name: Xcode Result Types -# Version: 3.30 -# Signature: Vu9Uty9iL1U= +# Version: 3.34 +# Signature: GSHghHjCrb8= require 'time' module XCResult module Models - # - ActionAbstractTestSummary - # * Kind: object - # * Properties: - # + name: String? class ActionAbstractTestSummary # @return [String, nil] name attr_reader :name @@ -22,63 +18,42 @@ def initialize(data) end end - # - ActionDeviceRecord - # * Kind: object - # * Properties: - # + name: String - # + isConcreteDevice: Bool - # + operatingSystemVersion: String - # + operatingSystemVersionWithBuildNumber: String - # + nativeArchitecture: String - # + modelName: String - # + modelCode: String - # + modelUTI: String - # + identifier: String - # + isWireless: Bool - # + cpuKind: String - # + cpuCount: Int? - # + cpuSpeedInMHz: Int? - # + busSpeedInMHz: Int? - # + ramSizeInMegabytes: Int? - # + physicalCPUCoresPerPackage: Int? - # + logicalCPUCoresPerPackage: Int? - # + platformRecord: ActionPlatformRecord class ActionDeviceRecord # @return [String] name attr_reader :name - # @return [Bool] isConcreteDevice + # @return [Bool] is_concrete_device attr_reader :is_concrete_device - # @return [String] operatingSystemVersion + # @return [String] operating_system_version attr_reader :operating_system_version - # @return [String] operatingSystemVersionWithBuildNumber + # @return [String] operating_system_version_with_build_number attr_reader :operating_system_version_with_build_number - # @return [String] nativeArchitecture + # @return [String] native_architecture attr_reader :native_architecture - # @return [String] modelName + # @return [String] model_name attr_reader :model_name - # @return [String] modelCode + # @return [String] model_code attr_reader :model_code - # @return [String] modelUTI + # @return [String] model_uti attr_reader :model_uti # @return [String] identifier attr_reader :identifier - # @return [Bool] isWireless + # @return [Bool] is_wireless attr_reader :is_wireless - # @return [String] cpuKind + # @return [String] cpu_kind attr_reader :cpu_kind - # @return [Int, nil] cpuCount + # @return [Int, nil] cpu_count attr_reader :cpu_count - # @return [Int, nil] cpuSpeedInMHz + # @return [Int, nil] cpu_speed_in_mhz attr_reader :cpu_speed_in_mhz - # @return [Int, nil] busSpeedInMHz + # @return [Int, nil] bus_speed_in_mhz attr_reader :bus_speed_in_mhz - # @return [Int, nil] ramSizeInMegabytes + # @return [Int, nil] ram_size_in_megabytes attr_reader :ram_size_in_megabytes - # @return [Int, nil] physicalCPUCoresPerPackage + # @return [Int, nil] physical_cpu_cores_per_package attr_reader :physical_cpu_cores_per_package - # @return [Int, nil] logicalCPUCoresPerPackage + # @return [Int, nil] logical_cpu_cores_per_package attr_reader :logical_cpu_cores_per_package - # @return [ActionPlatformRecord] platformRecord + # @return [ActionPlatformRecord] platform_record attr_reader :platform_record def initialize(data) @@ -93,25 +68,20 @@ def initialize(data) @identifier = data.dig('identifier', '_value') @is_wireless = data.dig('isWireless', '_value') @cpu_kind = data.dig('cpuKind', '_value') - @cpu_count = data.dig('cpuCount', '_value').to_i if data['cpuCount'] - @cpu_speed_in_mhz = data.dig('cpuSpeedInMHz', '_value').to_i if data['cpuSpeedInMHz'] - @bus_speed_in_mhz = data.dig('busSpeedInMHz', '_value').to_i if data['busSpeedInMHz'] - @ram_size_in_megabytes = data.dig('ramSizeInMegabytes', '_value').to_i if data['ramSizeInMegabytes'] - @physical_cpu_cores_per_package = data.dig('physicalCPUCoresPerPackage', '_value').to_i if data['physicalCPUCoresPerPackage'] - @logical_cpu_cores_per_package = data.dig('logicalCPUCoresPerPackage', '_value').to_i if data['logicalCPUCoresPerPackage'] + @cpu_count = data.dig('cpuCount', '_value') if data['cpuCount'] + @cpu_speed_in_mhz = data.dig('cpuSpeedInMHz', '_value') if data['cpuSpeedInMHz'] + @bus_speed_in_mhz = data.dig('busSpeedInMHz', '_value') if data['busSpeedInMHz'] + @ram_size_in_megabytes = data.dig('ramSizeInMegabytes', '_value') if data['ramSizeInMegabytes'] + @physical_cpu_cores_per_package = data.dig('physicalCPUCoresPerPackage', '_value') if data['physicalCPUCoresPerPackage'] + @logical_cpu_cores_per_package = data.dig('logicalCPUCoresPerPackage', '_value') if data['logicalCPUCoresPerPackage'] @platform_record = Models.load_class(data.dig('platformRecord', '_type', '_name')).new(data.dig('platformRecord')) end end - # - ActionPlatformRecord - # * Kind: object - # * Properties: - # + identifier: String - # + userDescription: String class ActionPlatformRecord # @return [String] identifier attr_reader :identifier - # @return [String] userDescription + # @return [String] user_description attr_reader :user_description def initialize(data) @@ -120,61 +90,38 @@ def initialize(data) end end - # - ActionRecord - # * Kind: object - # * Properties: - # + schemeCommandName: String - # + schemeTaskName: String - # + title: String? - # + startedTime: Date - # + endedTime: Date - # + runDestination: ActionRunDestinationRecord - # + buildResult: ActionResult - # + actionResult: ActionResult class ActionRecord - # @return [String] schemeCommandName + # @return [String] scheme_command_name attr_reader :scheme_command_name - # @return [String] schemeTaskName + # @return [String] scheme_task_name attr_reader :scheme_task_name # @return [String, nil] title attr_reader :title - # @return [Date] startedTime + # @return [Date] started_time attr_reader :started_time - # @return [Date] endedTime + # @return [Date] ended_time attr_reader :ended_time - # @return [ActionRunDestinationRecord] runDestination + # @return [ActionRunDestinationRecord] run_destination attr_reader :run_destination - # @return [ActionResult] buildResult + # @return [ActionResult] build_result attr_reader :build_result - # @return [ActionResult] actionResult + # @return [ActionResult] action_result attr_reader :action_result def initialize(data) @scheme_command_name = data.dig('schemeCommandName', '_value') @scheme_task_name = data.dig('schemeTaskName', '_value') @title = data.dig('title', '_value') if data['title'] - @started_time = Time.parse(data.dig('startedTime', '_value')) - @ended_time = Time.parse(data.dig('endedTime', '_value')) + @started_time = data.dig('startedTime', '_value') + @ended_time = data.dig('endedTime', '_value') @run_destination = Models.load_class(data.dig('runDestination', '_type', '_name')).new(data.dig('runDestination')) @build_result = Models.load_class(data.dig('buildResult', '_type', '_name')).new(data.dig('buildResult')) @action_result = Models.load_class(data.dig('actionResult', '_type', '_name')).new(data.dig('actionResult')) end end - # - ActionResult - # * Kind: object - # * Properties: - # + resultName: String - # + status: String - # + metrics: ResultMetrics - # + issues: ResultIssueSummaries - # + coverage: CodeCoverageInfo - # + timelineRef: Reference? - # + logRef: Reference? - # + testsRef: Reference? - # + diagnosticsRef: Reference? class ActionResult - # @return [String] resultName + # @return [String] result_name attr_reader :result_name # @return [String] status attr_reader :status @@ -184,13 +131,13 @@ class ActionResult attr_reader :issues # @return [CodeCoverageInfo] coverage attr_reader :coverage - # @return [Reference, nil] timelineRef + # @return [Reference, nil] timeline_ref attr_reader :timeline_ref - # @return [Reference, nil] logRef + # @return [Reference, nil] log_ref attr_reader :log_ref - # @return [Reference, nil] testsRef + # @return [Reference, nil] tests_ref attr_reader :tests_ref - # @return [Reference, nil] diagnosticsRef + # @return [Reference, nil] diagnostics_ref attr_reader :diagnostics_ref def initialize(data) @@ -206,24 +153,16 @@ def initialize(data) end end - # - ActionRunDestinationRecord - # * Kind: object - # * Properties: - # + displayName: String - # + targetArchitecture: String - # + targetDeviceRecord: ActionDeviceRecord - # + localComputerRecord: ActionDeviceRecord - # + targetSDKRecord: ActionSDKRecord class ActionRunDestinationRecord - # @return [String] displayName + # @return [String] display_name attr_reader :display_name - # @return [String] targetArchitecture + # @return [String] target_architecture attr_reader :target_architecture - # @return [ActionDeviceRecord] targetDeviceRecord + # @return [ActionDeviceRecord] target_device_record attr_reader :target_device_record - # @return [ActionDeviceRecord] localComputerRecord + # @return [ActionDeviceRecord] local_computer_record attr_reader :local_computer_record - # @return [ActionSDKRecord] targetSDKRecord + # @return [ActionSDKRecord] target_sdk_record attr_reader :target_sdk_record def initialize(data) @@ -235,21 +174,14 @@ def initialize(data) end end - # - ActionSDKRecord - # * Kind: object - # * Properties: - # + name: String - # + identifier: String - # + operatingSystemVersion: String - # + isInternal: Bool class ActionSDKRecord # @return [String] name attr_reader :name # @return [String] identifier attr_reader :identifier - # @return [String] operatingSystemVersion + # @return [String] operating_system_version attr_reader :operating_system_version - # @return [Bool] isInternal + # @return [Bool] is_internal attr_reader :is_internal def initialize(data) @@ -260,22 +192,10 @@ def initialize(data) end end - # - ActionTestActivitySummary - # * Kind: object - # * Properties: - # + title: String - # + activityType: String - # + uuid: String - # + start: Date? - # + finish: Date? - # + attachments: [ActionTestAttachment] - # + subactivities: [ActionTestActivitySummary] - # + failureSummaryIDs: [String] - # + expectedFailureIDs: [String] class ActionTestActivitySummary # @return [String] title attr_reader :title - # @return [String] activityType + # @return [String] activity_type attr_reader :activity_type # @return [String] uuid attr_reader :uuid @@ -287,17 +207,17 @@ class ActionTestActivitySummary attr_reader :attachments # @return [Array] subactivities attr_reader :subactivities - # @return [Array] failureSummaryIDs + # @return [Array] failure_summary_ids attr_reader :failure_summary_ids - # @return [Array] expectedFailureIDs + # @return [Array] expected_failure_ids attr_reader :expected_failure_ids def initialize(data) @title = data.dig('title', '_value') @activity_type = data.dig('activityType', '_value') @uuid = data.dig('uuid', '_value') - @start = Time.parse(data.dig('start', '_value')) if data['start'] - @finish = Time.parse(data.dig('finish', '_value')) if data['finish'] + @start = data.dig('start', '_value') if data['start'] + @finish = data.dig('finish', '_value') if data['finish'] @attachments = (data.dig('attachments', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } @subactivities = (data.dig('subactivities', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } @failure_summary_ids = (data.dig('failureSummaryIDs', '_values') || []).map {|d| d.dig('failureSummaryIDs', '_value') } @@ -305,66 +225,59 @@ def initialize(data) end end - # - ActionTestAttachment - # * Kind: object - # * Properties: - # + uniformTypeIdentifier: String - # + name: String? - # + timestamp: Date? - # + userInfo: SortedKeyValueArray? - # + lifetime: String - # + inActivityIdentifier: Int - # + filename: String? - # + payloadRef: Reference? - # + payloadSize: Int class ActionTestAttachment - # @return [String] uniformTypeIdentifier + # @return [String] uniform_type_identifier attr_reader :uniform_type_identifier # @return [String, nil] name attr_reader :name + # @return [String, nil] uuid + attr_reader :uuid # @return [Date, nil] timestamp attr_reader :timestamp - # @return [SortedKeyValueArray, nil] userInfo + # @return [SortedKeyValueArray, nil] user_info attr_reader :user_info # @return [String] lifetime attr_reader :lifetime - # @return [Int] inActivityIdentifier + # @return [Int] in_activity_identifier attr_reader :in_activity_identifier # @return [String, nil] filename attr_reader :filename - # @return [Reference, nil] payloadRef + # @return [Reference, nil] payload_ref attr_reader :payload_ref - # @return [Int] payloadSize + # @return [Int] payload_size attr_reader :payload_size def initialize(data) @uniform_type_identifier = data.dig('uniformTypeIdentifier', '_value') @name = data.dig('name', '_value') if data['name'] - @timestamp = Time.parse(data.dig('timestamp', '_value')) if data['timestamp'] + @uuid = data.dig('uuid', '_value') if data['uuid'] + @timestamp = data.dig('timestamp', '_value') if data['timestamp'] @user_info = Models.load_class(data.dig('userInfo', '_type', '_name')).new(data.dig('userInfo')) if data['userInfo'] @lifetime = data.dig('lifetime', '_value') - @in_activity_identifier = data.dig('inActivityIdentifier', '_value').to_i + @in_activity_identifier = data.dig('inActivityIdentifier', '_value') @filename = data.dig('filename', '_value') if data['filename'] @payload_ref = Models.load_class(data.dig('payloadRef', '_type', '_name')).new(data.dig('payloadRef')) if data['payloadRef'] - @payload_size = data.dig('payloadSize', '_value').to_i + @payload_size = data.dig('payloadSize', '_value') + end + end + + class ActionTestConfiguration + # @return [SortedKeyValueArray] values + attr_reader :values + + def initialize(data) + @values = Models.load_class(data.dig('values', '_type', '_name')).new(data.dig('values')) end end - # - ActionTestExpectedFailure - # * Kind: object - # * Properties: - # + uuid: String - # + failureReason: String? - # + failureSummary: ActionTestFailureSummary? - # + isTopLevelFailure: Bool class ActionTestExpectedFailure # @return [String] uuid attr_reader :uuid - # @return [String, nil] failureReason + # @return [String, nil] failure_reason attr_reader :failure_reason - # @return [ActionTestFailureSummary, nil] failureSummary + # @return [ActionTestFailureSummary, nil] failure_summary attr_reader :failure_summary - # @return [Bool] isTopLevelFailure + # @return [Bool] is_top_level_failure attr_reader :is_top_level_failure def initialize(data) @@ -375,51 +288,36 @@ def initialize(data) end end - # - ActionTestFailureSummary - # * Kind: object - # * Properties: - # + message: String? - # + fileName: String - # + lineNumber: Int - # + isPerformanceFailure: Bool - # + uuid: String - # + issueType: String? - # + detailedDescription: String? - # + attachments: [ActionTestAttachment] - # + associatedError: TestAssociatedError? - # + sourceCodeContext: SourceCodeContext? - # + timestamp: Date? - # + isTopLevelFailure: Bool class ActionTestFailureSummary # @return [String, nil] message attr_reader :message - # @return [String] fileName + # @return [String] file_name attr_reader :file_name - # @return [Int] lineNumber + # @return [Int] line_number attr_reader :line_number - # @return [Bool] isPerformanceFailure + # @return [Bool] is_performance_failure attr_reader :is_performance_failure # @return [String] uuid attr_reader :uuid - # @return [String, nil] issueType + # @return [String, nil] issue_type attr_reader :issue_type - # @return [String, nil] detailedDescription + # @return [String, nil] detailed_description attr_reader :detailed_description # @return [Array] attachments attr_reader :attachments - # @return [TestAssociatedError, nil] associatedError + # @return [TestAssociatedError, nil] associated_error attr_reader :associated_error - # @return [SourceCodeContext, nil] sourceCodeContext + # @return [SourceCodeContext, nil] source_code_context attr_reader :source_code_context # @return [Date, nil] timestamp attr_reader :timestamp - # @return [Bool] isTopLevelFailure + # @return [Bool] is_top_level_failure attr_reader :is_top_level_failure def initialize(data) @message = data.dig('message', '_value') if data['message'] @file_name = data.dig('fileName', '_value') - @line_number = data.dig('lineNumber', '_value').to_i + @line_number = data.dig('lineNumber', '_value') @is_performance_failure = data.dig('isPerformanceFailure', '_value') @uuid = data.dig('uuid', '_value') @issue_type = data.dig('issueType', '_value') if data['issueType'] @@ -427,16 +325,11 @@ def initialize(data) @attachments = (data.dig('attachments', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } @associated_error = Models.load_class(data.dig('associatedError', '_type', '_name')).new(data.dig('associatedError')) if data['associatedError'] @source_code_context = Models.load_class(data.dig('sourceCodeContext', '_type', '_name')).new(data.dig('sourceCodeContext')) if data['sourceCodeContext'] - @timestamp = Time.parse(data.dig('timestamp', '_value')) if data['timestamp'] + @timestamp = data.dig('timestamp', '_value') if data['timestamp'] @is_top_level_failure = data.dig('isTopLevelFailure', '_value') end end - # - ActionTestSummaryIdentifiableObject - # * Supertype: ActionAbstractTestSummary - # * Kind: object - # * Properties: - # + identifier: String? class ActionTestSummaryIdentifiableObject < ActionAbstractTestSummary # @return [String, nil] identifier attr_reader :identifier @@ -447,96 +340,66 @@ def initialize(data) end end - # - ActionTestMetadata - # * Supertype: ActionTestSummaryIdentifiableObject - # * Kind: object - # * Properties: - # + testStatus: String - # + duration: Double? - # + summaryRef: Reference? - # + performanceMetricsCount: Int - # + failureSummariesCount: Int - # + activitySummariesCount: Int class ActionTestMetadata < ActionTestSummaryIdentifiableObject - # @return [String] testStatus + # @return [String] test_status attr_reader :test_status # @return [Double, nil] duration attr_reader :duration - # @return [Reference, nil] summaryRef + # @return [Reference, nil] summary_ref attr_reader :summary_ref - # @return [Int] performanceMetricsCount + # @return [Int] performance_metrics_count attr_reader :performance_metrics_count - # @return [Int] failureSummariesCount + # @return [Int] failure_summaries_count attr_reader :failure_summaries_count - # @return [Int] activitySummariesCount + # @return [Int] activity_summaries_count attr_reader :activity_summaries_count def initialize(data) @test_status = data.dig('testStatus', '_value') - @duration = data.dig('duration', '_value').to_f if data['duration'] + @duration = data.dig('duration', '_value') if data['duration'] @summary_ref = Models.load_class(data.dig('summaryRef', '_type', '_name')).new(data.dig('summaryRef')) if data['summaryRef'] - @performance_metrics_count = data.dig('performanceMetricsCount', '_value').to_i - @failure_summaries_count = data.dig('failureSummariesCount', '_value').to_i - @activity_summaries_count = data.dig('activitySummariesCount', '_value').to_i + @performance_metrics_count = data.dig('performanceMetricsCount', '_value') + @failure_summaries_count = data.dig('failureSummariesCount', '_value') + @activity_summaries_count = data.dig('activitySummariesCount', '_value') super end end - # - ActionTestNoticeSummary - # * Kind: object - # * Properties: - # + message: String? - # + fileName: String - # + lineNumber: Int class ActionTestNoticeSummary # @return [String, nil] message attr_reader :message - # @return [String] fileName + # @return [String] file_name attr_reader :file_name - # @return [Int] lineNumber + # @return [Int] line_number attr_reader :line_number def initialize(data) @message = data.dig('message', '_value') if data['message'] @file_name = data.dig('fileName', '_value') - @line_number = data.dig('lineNumber', '_value').to_i + @line_number = data.dig('lineNumber', '_value') end end - # - ActionTestPerformanceMetricSummary - # * Kind: object - # * Properties: - # + displayName: String - # + unitOfMeasurement: String - # + measurements: [Double] - # + identifier: String? - # + baselineName: String? - # + baselineAverage: Double? - # + maxPercentRegression: Double? - # + maxPercentRelativeStandardDeviation: Double? - # + maxRegression: Double? - # + maxStandardDeviation: Double? - # + polarity: String? class ActionTestPerformanceMetricSummary - # @return [String] displayName + # @return [String] display_name attr_reader :display_name - # @return [String] unitOfMeasurement + # @return [String] unit_of_measurement attr_reader :unit_of_measurement # @return [Array] measurements attr_reader :measurements # @return [String, nil] identifier attr_reader :identifier - # @return [String, nil] baselineName + # @return [String, nil] baseline_name attr_reader :baseline_name - # @return [Double, nil] baselineAverage + # @return [Double, nil] baseline_average attr_reader :baseline_average - # @return [Double, nil] maxPercentRegression + # @return [Double, nil] max_percent_regression attr_reader :max_percent_regression - # @return [Double, nil] maxPercentRelativeStandardDeviation + # @return [Double, nil] max_percent_relative_standard_deviation attr_reader :max_percent_relative_standard_deviation - # @return [Double, nil] maxRegression + # @return [Double, nil] max_regression attr_reader :max_regression - # @return [Double, nil] maxStandardDeviation + # @return [Double, nil] max_standard_deviation attr_reader :max_standard_deviation # @return [String, nil] polarity attr_reader :polarity @@ -544,22 +407,18 @@ class ActionTestPerformanceMetricSummary def initialize(data) @display_name = data.dig('displayName', '_value') @unit_of_measurement = data.dig('unitOfMeasurement', '_value') - @measurements = (data.dig('measurements', '_values') || []).map {|d| d.dig('measurements', '_value').to_f } + @measurements = (data.dig('measurements', '_values') || []).map {|d| d.dig('measurements', '_value') } @identifier = data.dig('identifier', '_value') if data['identifier'] @baseline_name = data.dig('baselineName', '_value') if data['baselineName'] - @baseline_average = data.dig('baselineAverage', '_value').to_f if data['baselineAverage'] - @max_percent_regression = data.dig('maxPercentRegression', '_value').to_f if data['maxPercentRegression'] - @max_percent_relative_standard_deviation = data.dig('maxPercentRelativeStandardDeviation', '_value').to_f if data['maxPercentRelativeStandardDeviation'] - @max_regression = data.dig('maxRegression', '_value').to_f if data['maxRegression'] - @max_standard_deviation = data.dig('maxStandardDeviation', '_value').to_f if data['maxStandardDeviation'] + @baseline_average = data.dig('baselineAverage', '_value') if data['baselineAverage'] + @max_percent_regression = data.dig('maxPercentRegression', '_value') if data['maxPercentRegression'] + @max_percent_relative_standard_deviation = data.dig('maxPercentRelativeStandardDeviation', '_value') if data['maxPercentRelativeStandardDeviation'] + @max_regression = data.dig('maxRegression', '_value') if data['maxRegression'] + @max_standard_deviation = data.dig('maxStandardDeviation', '_value') if data['maxStandardDeviation'] @polarity = data.dig('polarity', '_value') if data['polarity'] end end - # - ActionTestPlanRunSummaries - # * Kind: object - # * Properties: - # + summaries: [ActionTestPlanRunSummary] class ActionTestPlanRunSummaries # @return [Array] summaries attr_reader :summaries @@ -569,13 +428,8 @@ def initialize(data) end end - # - ActionTestPlanRunSummary - # * Supertype: ActionAbstractTestSummary - # * Kind: object - # * Properties: - # + testableSummaries: [ActionTestableSummary] class ActionTestPlanRunSummary < ActionAbstractTestSummary - # @return [Array] testableSummaries + # @return [Array] testable_summaries attr_reader :testable_summaries def initialize(data) @@ -584,76 +438,55 @@ def initialize(data) end end - # - ActionTestRepetitionPolicySummary - # * Kind: object - # * Properties: - # + iteration: Int? - # + totalIterations: Int? - # + repetitionMode: String? class ActionTestRepetitionPolicySummary # @return [Int, nil] iteration attr_reader :iteration - # @return [Int, nil] totalIterations + # @return [Int, nil] total_iterations attr_reader :total_iterations - # @return [String, nil] repetitionMode + # @return [String, nil] repetition_mode attr_reader :repetition_mode def initialize(data) - @iteration = data.dig('iteration', '_value').to_i if data['iteration'] - @total_iterations = data.dig('totalIterations', '_value').to_i if data['totalIterations'] + @iteration = data.dig('iteration', '_value') if data['iteration'] + @total_iterations = data.dig('totalIterations', '_value') if data['totalIterations'] @repetition_mode = data.dig('repetitionMode', '_value') if data['repetitionMode'] end end - # - ActionTestSummary - # * Supertype: ActionTestSummaryIdentifiableObject - # * Kind: object - # * Properties: - # + testStatus: String - # + duration: Double - # + performanceMetrics: [ActionTestPerformanceMetricSummary] - # + failureSummaries: [ActionTestFailureSummary] - # + expectedFailures: [ActionTestExpectedFailure] - # + skipNoticeSummary: ActionTestNoticeSummary? - # + activitySummaries: [ActionTestActivitySummary] - # + repetitionPolicySummary: ActionTestRepetitionPolicySummary? class ActionTestSummary < ActionTestSummaryIdentifiableObject - # @return [String] testStatus + # @return [String] test_status attr_reader :test_status # @return [Double] duration attr_reader :duration - # @return [Array] performanceMetrics + # @return [Array] performance_metrics attr_reader :performance_metrics - # @return [Array] failureSummaries + # @return [Array] failure_summaries attr_reader :failure_summaries - # @return [Array] expectedFailures + # @return [Array] expected_failures attr_reader :expected_failures - # @return [ActionTestNoticeSummary, nil] skipNoticeSummary + # @return [ActionTestNoticeSummary, nil] skip_notice_summary attr_reader :skip_notice_summary - # @return [Array] activitySummaries + # @return [Array] activity_summaries attr_reader :activity_summaries - # @return [ActionTestRepetitionPolicySummary, nil] repetitionPolicySummary + # @return [ActionTestRepetitionPolicySummary, nil] repetition_policy_summary attr_reader :repetition_policy_summary + # @return [ActionTestConfiguration, nil] configuration + attr_reader :configuration def initialize(data) @test_status = data.dig('testStatus', '_value') - @duration = data.dig('duration', '_value').to_f + @duration = data.dig('duration', '_value') @performance_metrics = (data.dig('performanceMetrics', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } @failure_summaries = (data.dig('failureSummaries', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } @expected_failures = (data.dig('expectedFailures', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } @skip_notice_summary = Models.load_class(data.dig('skipNoticeSummary', '_type', '_name')).new(data.dig('skipNoticeSummary')) if data['skipNoticeSummary'] @activity_summaries = (data.dig('activitySummaries', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } @repetition_policy_summary = Models.load_class(data.dig('repetitionPolicySummary', '_type', '_name')).new(data.dig('repetitionPolicySummary')) if data['repetitionPolicySummary'] + @configuration = Models.load_class(data.dig('configuration', '_type', '_name')).new(data.dig('configuration')) if data['configuration'] super end end - # - ActionTestSummaryGroup - # * Supertype: ActionTestSummaryIdentifiableObject - # * Kind: object - # * Properties: - # + duration: Double - # + subtests: [ActionTestSummaryIdentifiableObject] class ActionTestSummaryGroup < ActionTestSummaryIdentifiableObject # @return [Double] duration attr_reader :duration @@ -661,40 +494,28 @@ class ActionTestSummaryGroup < ActionTestSummaryIdentifiableObject attr_reader :subtests def initialize(data) - @duration = data.dig('duration', '_value').to_f + @duration = data.dig('duration', '_value') @subtests = (data.dig('subtests', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } super end end - # - ActionTestableSummary - # * Supertype: ActionAbstractTestSummary - # * Kind: object - # * Properties: - # + projectRelativePath: String? - # + targetName: String? - # + testKind: String? - # + tests: [ActionTestSummaryIdentifiableObject] - # + diagnosticsDirectoryName: String? - # + failureSummaries: [ActionTestFailureSummary] - # + testLanguage: String? - # + testRegion: String? class ActionTestableSummary < ActionAbstractTestSummary - # @return [String, nil] projectRelativePath + # @return [String, nil] project_relative_path attr_reader :project_relative_path - # @return [String, nil] targetName + # @return [String, nil] target_name attr_reader :target_name - # @return [String, nil] testKind + # @return [String, nil] test_kind attr_reader :test_kind # @return [Array] tests attr_reader :tests - # @return [String, nil] diagnosticsDirectoryName + # @return [String, nil] diagnostics_directory_name attr_reader :diagnostics_directory_name - # @return [Array] failureSummaries + # @return [Array] failure_summaries attr_reader :failure_summaries - # @return [String, nil] testLanguage + # @return [String, nil] test_language attr_reader :test_language - # @return [String, nil] testRegion + # @return [String, nil] test_region attr_reader :test_region def initialize(data) @@ -710,18 +531,12 @@ def initialize(data) end end - # - ActionsInvocationMetadata - # * Kind: object - # * Properties: - # + creatingWorkspaceFilePath: String - # + uniqueIdentifier: String - # + schemeIdentifier: EntityIdentifier? class ActionsInvocationMetadata - # @return [String] creatingWorkspaceFilePath + # @return [String] creating_workspace_file_path attr_reader :creating_workspace_file_path - # @return [String] uniqueIdentifier + # @return [String] unique_identifier attr_reader :unique_identifier - # @return [EntityIdentifier, nil] schemeIdentifier + # @return [EntityIdentifier, nil] scheme_identifier attr_reader :scheme_identifier def initialize(data) @@ -731,16 +546,8 @@ def initialize(data) end end - # - ActionsInvocationRecord - # * Kind: object - # * Properties: - # + metadataRef: Reference? - # + metrics: ResultMetrics - # + issues: ResultIssueSummaries - # + actions: [ActionRecord] - # + archive: ArchiveInfo? class ActionsInvocationRecord - # @return [Reference, nil] metadataRef + # @return [Reference, nil] metadata_ref attr_reader :metadata_ref # @return [ResultMetrics] metrics attr_reader :metrics @@ -760,33 +567,21 @@ def initialize(data) end end - # - ActivityLogAnalyzerStep - # * Kind: object - # * Properties: - # + parentIndex: Int class ActivityLogAnalyzerStep - # @return [Int] parentIndex + # @return [Int] parent_index attr_reader :parent_index def initialize(data) - @parent_index = data.dig('parentIndex', '_value').to_i + @parent_index = data.dig('parentIndex', '_value') end end - # - ActivityLogAnalyzerControlFlowStep - # * Supertype: ActivityLogAnalyzerStep - # * Kind: object - # * Properties: - # + title: String - # + startLocation: DocumentLocation? - # + endLocation: DocumentLocation? - # + edges: [ActivityLogAnalyzerControlFlowStepEdge] class ActivityLogAnalyzerControlFlowStep < ActivityLogAnalyzerStep # @return [String] title attr_reader :title - # @return [DocumentLocation, nil] startLocation + # @return [DocumentLocation, nil] start_location attr_reader :start_location - # @return [DocumentLocation, nil] endLocation + # @return [DocumentLocation, nil] end_location attr_reader :end_location # @return [Array] edges attr_reader :edges @@ -800,15 +595,10 @@ def initialize(data) end end - # - ActivityLogAnalyzerControlFlowStepEdge - # * Kind: object - # * Properties: - # + startLocation: DocumentLocation? - # + endLocation: DocumentLocation? class ActivityLogAnalyzerControlFlowStepEdge - # @return [DocumentLocation, nil] startLocation + # @return [DocumentLocation, nil] start_location attr_reader :start_location - # @return [DocumentLocation, nil] endLocation + # @return [DocumentLocation, nil] end_location attr_reader :end_location def initialize(data) @@ -817,14 +607,6 @@ def initialize(data) end end - # - ActivityLogAnalyzerEventStep - # * Supertype: ActivityLogAnalyzerStep - # * Kind: object - # * Properties: - # + title: String - # + location: DocumentLocation? - # + description: String - # + callDepth: Int class ActivityLogAnalyzerEventStep < ActivityLogAnalyzerStep # @return [String] title attr_reader :title @@ -832,33 +614,24 @@ class ActivityLogAnalyzerEventStep < ActivityLogAnalyzerStep attr_reader :location # @return [String] description attr_reader :description - # @return [Int] callDepth + # @return [Int] call_depth attr_reader :call_depth def initialize(data) @title = data.dig('title', '_value') @location = Models.load_class(data.dig('location', '_type', '_name')).new(data.dig('location')) if data['location'] @description = data.dig('description', '_value') - @call_depth = data.dig('callDepth', '_value').to_i + @call_depth = data.dig('callDepth', '_value') super end end - # - ActivityLogMessage - # * Kind: object - # * Properties: - # + type: String - # + title: String - # + shortTitle: String? - # + category: String? - # + location: DocumentLocation? - # + annotations: [ActivityLogMessageAnnotation] class ActivityLogMessage # @return [String] type attr_reader :type # @return [String] title attr_reader :title - # @return [String, nil] shortTitle + # @return [String, nil] short_title attr_reader :short_title # @return [String, nil] category attr_reader :category @@ -877,32 +650,22 @@ def initialize(data) end end - # - ActivityLogAnalyzerResultMessage - # * Supertype: ActivityLogMessage - # * Kind: object - # * Properties: - # + steps: [ActivityLogAnalyzerStep] - # + resultType: String? - # + keyEventIndex: Int class ActivityLogAnalyzerResultMessage < ActivityLogMessage # @return [Array] steps attr_reader :steps - # @return [String, nil] resultType + # @return [String, nil] result_type attr_reader :result_type - # @return [Int] keyEventIndex + # @return [Int] key_event_index attr_reader :key_event_index def initialize(data) @steps = (data.dig('steps', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } @result_type = data.dig('resultType', '_value') if data['resultType'] - @key_event_index = data.dig('keyEventIndex', '_value').to_i + @key_event_index = data.dig('keyEventIndex', '_value') super end end - # - ActivityLogAnalyzerWarningMessage - # * Supertype: ActivityLogMessage - # * Kind: object class ActivityLogAnalyzerWarningMessage < ActivityLogMessage def initialize(data) @@ -910,23 +673,12 @@ def initialize(data) end end - # - ActivityLogSection - # * Kind: object - # * Properties: - # + domainType: String - # + title: String - # + startTime: Date? - # + duration: Double - # + result: String? - # + location: DocumentLocation? - # + subsections: [ActivityLogSection] - # + messages: [ActivityLogMessage] class ActivityLogSection - # @return [String] domainType + # @return [String] domain_type attr_reader :domain_type # @return [String] title attr_reader :title - # @return [Date, nil] startTime + # @return [Date, nil] start_time attr_reader :start_time # @return [Double] duration attr_reader :duration @@ -942,8 +694,8 @@ class ActivityLogSection def initialize(data) @domain_type = data.dig('domainType', '_value') @title = data.dig('title', '_value') - @start_time = Time.parse(data.dig('startTime', '_value')) if data['startTime'] - @duration = data.dig('duration', '_value').to_f + @start_time = data.dig('startTime', '_value') if data['startTime'] + @duration = data.dig('duration', '_value') @result = data.dig('result', '_value') if data['result'] @location = Models.load_class(data.dig('location', '_type', '_name')).new(data.dig('location')) if data['location'] @subsections = (data.dig('subsections', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } @@ -951,34 +703,22 @@ def initialize(data) end end - # - ActivityLogCommandInvocationSection - # * Supertype: ActivityLogSection - # * Kind: object - # * Properties: - # + commandDetails: String - # + emittedOutput: String - # + exitCode: Int? class ActivityLogCommandInvocationSection < ActivityLogSection - # @return [String] commandDetails + # @return [String] command_details attr_reader :command_details - # @return [String] emittedOutput + # @return [String] emitted_output attr_reader :emitted_output - # @return [Int, nil] exitCode + # @return [Int, nil] exit_code attr_reader :exit_code def initialize(data) @command_details = data.dig('commandDetails', '_value') @emitted_output = data.dig('emittedOutput', '_value') - @exit_code = data.dig('exitCode', '_value').to_i if data['exitCode'] + @exit_code = data.dig('exitCode', '_value') if data['exitCode'] super end end - # - ActivityLogMajorSection - # * Supertype: ActivityLogSection - # * Kind: object - # * Properties: - # + subtitle: String class ActivityLogMajorSection < ActivityLogSection # @return [String] subtitle attr_reader :subtitle @@ -989,11 +729,6 @@ def initialize(data) end end - # - ActivityLogMessageAnnotation - # * Kind: object - # * Properties: - # + title: String - # + location: DocumentLocation? class ActivityLogMessageAnnotation # @return [String] title attr_reader :title @@ -1006,13 +741,8 @@ def initialize(data) end end - # - ActivityLogTargetBuildSection - # * Supertype: ActivityLogMajorSection - # * Kind: object - # * Properties: - # + productType: String? class ActivityLogTargetBuildSection < ActivityLogMajorSection - # @return [String, nil] productType + # @return [String, nil] product_type attr_reader :product_type def initialize(data) @@ -1021,37 +751,24 @@ def initialize(data) end end - # - ActivityLogUnitTestSection - # * Supertype: ActivityLogSection - # * Kind: object - # * Properties: - # + testName: String? - # + suiteName: String? - # + summary: String? - # + emittedOutput: String? - # + performanceTestOutput: String? - # + testsPassedString: String? - # + wasSkipped: Bool - # + runnablePath: String? - # + runnableUTI: String? class ActivityLogUnitTestSection < ActivityLogSection - # @return [String, nil] testName + # @return [String, nil] test_name attr_reader :test_name - # @return [String, nil] suiteName + # @return [String, nil] suite_name attr_reader :suite_name # @return [String, nil] summary attr_reader :summary - # @return [String, nil] emittedOutput + # @return [String, nil] emitted_output attr_reader :emitted_output - # @return [String, nil] performanceTestOutput + # @return [String, nil] performance_test_output attr_reader :performance_test_output - # @return [String, nil] testsPassedString + # @return [String, nil] tests_passed_string attr_reader :tests_passed_string - # @return [Bool] wasSkipped + # @return [Bool] was_skipped attr_reader :was_skipped - # @return [String, nil] runnablePath + # @return [String, nil] runnable_path attr_reader :runnable_path - # @return [String, nil] runnableUTI + # @return [String, nil] runnable_uti attr_reader :runnable_uti def initialize(data) @@ -1068,10 +785,6 @@ def initialize(data) end end - # - ArchiveInfo - # * Kind: object - # * Properties: - # + path: String? class ArchiveInfo # @return [String, nil] path attr_reader :path @@ -1081,34 +794,18 @@ def initialize(data) end end - # - Array - # * Kind: array class Array def initialize(data) end end - # - Bool - # * Kind: value - class Bool - - def initialize(data) - end - end - - # - CodeCoverageInfo - # * Kind: object - # * Properties: - # + hasCoverageData: Bool - # + reportRef: Reference? - # + archiveRef: Reference? class CodeCoverageInfo - # @return [Bool] hasCoverageData + # @return [Bool] has_coverage_data attr_reader :has_coverage_data - # @return [Reference, nil] reportRef + # @return [Reference, nil] report_ref attr_reader :report_ref - # @return [Reference, nil] archiveRef + # @return [Reference, nil] archive_ref attr_reader :archive_ref def initialize(data) @@ -1118,23 +815,10 @@ def initialize(data) end end - # - Date - # * Kind: value - class Date - - def initialize(data) - end - end - - # - DocumentLocation - # * Kind: object - # * Properties: - # + url: String - # + concreteTypeName: String class DocumentLocation # @return [String] url attr_reader :url - # @return [String] concreteTypeName + # @return [String] concrete_type_name attr_reader :concrete_type_name def initialize(data) @@ -1143,29 +827,14 @@ def initialize(data) end end - # - Double - # * Kind: value - class Double - - def initialize(data) - end - end - - # - EntityIdentifier - # * Kind: object - # * Properties: - # + entityName: String - # + containerName: String - # + entityType: String - # + sharedState: String class EntityIdentifier - # @return [String] entityName + # @return [String] entity_name attr_reader :entity_name - # @return [String] containerName + # @return [String] container_name attr_reader :container_name - # @return [String] entityType + # @return [String] entity_type attr_reader :entity_type - # @return [String] sharedState + # @return [String] shared_state attr_reader :shared_state def initialize(data) @@ -1176,29 +845,14 @@ def initialize(data) end end - # - Int - # * Kind: value - class Int - - def initialize(data) - end - end - - # - IssueSummary - # * Kind: object - # * Properties: - # + issueType: String - # + message: String - # + producingTarget: String? - # + documentLocationInCreatingWorkspace: DocumentLocation? class IssueSummary - # @return [String] issueType + # @return [String] issue_type attr_reader :issue_type # @return [String] message attr_reader :message - # @return [String, nil] producingTarget + # @return [String, nil] producing_target attr_reader :producing_target - # @return [DocumentLocation, nil] documentLocationInCreatingWorkspace + # @return [DocumentLocation, nil] document_location_in_creating_workspace attr_reader :document_location_in_creating_workspace def initialize(data) @@ -1209,10 +863,6 @@ def initialize(data) end end - # - ObjectID - # * Kind: object - # * Properties: - # + hash: String class ObjectID # @return [String] hash attr_reader :hash @@ -1222,15 +872,10 @@ def initialize(data) end end - # - Reference - # * Kind: object - # * Properties: - # + id: String - # + targetType: TypeDefinition? class Reference # @return [String] id attr_reader :id - # @return [TypeDefinition, nil] targetType + # @return [TypeDefinition, nil] target_type attr_reader :target_type def initialize(data) @@ -1239,21 +884,14 @@ def initialize(data) end end - # - ResultIssueSummaries - # * Kind: object - # * Properties: - # + analyzerWarningSummaries: [IssueSummary] - # + errorSummaries: [IssueSummary] - # + testFailureSummaries: [TestFailureIssueSummary] - # + warningSummaries: [IssueSummary] class ResultIssueSummaries - # @return [Array] analyzerWarningSummaries + # @return [Array] analyzer_warning_summaries attr_reader :analyzer_warning_summaries - # @return [Array] errorSummaries + # @return [Array] error_summaries attr_reader :error_summaries - # @return [Array] testFailureSummaries + # @return [Array] test_failure_summaries attr_reader :test_failure_summaries - # @return [Array] warningSummaries + # @return [Array] warning_summaries attr_reader :warning_summaries def initialize(data) @@ -1264,43 +902,30 @@ def initialize(data) end end - # - ResultMetrics - # * Kind: object - # * Properties: - # + analyzerWarningCount: Int - # + errorCount: Int - # + testsCount: Int - # + testsFailedCount: Int - # + testsSkippedCount: Int - # + warningCount: Int class ResultMetrics - # @return [Int] analyzerWarningCount + # @return [Int] analyzer_warning_count attr_reader :analyzer_warning_count - # @return [Int] errorCount + # @return [Int] error_count attr_reader :error_count - # @return [Int] testsCount + # @return [Int] tests_count attr_reader :tests_count - # @return [Int] testsFailedCount + # @return [Int] tests_failed_count attr_reader :tests_failed_count - # @return [Int] testsSkippedCount + # @return [Int] tests_skipped_count attr_reader :tests_skipped_count - # @return [Int] warningCount + # @return [Int] warning_count attr_reader :warning_count def initialize(data) - @analyzer_warning_count = data.dig('analyzerWarningCount', '_value').to_i - @error_count = data.dig('errorCount', '_value').to_i - @tests_count = data.dig('testsCount', '_value').to_i - @tests_failed_count = data.dig('testsFailedCount', '_value').to_i - @tests_skipped_count = data.dig('testsSkippedCount', '_value').to_i - @warning_count = data.dig('warningCount', '_value').to_i + @analyzer_warning_count = data.dig('analyzerWarningCount', '_value') + @error_count = data.dig('errorCount', '_value') + @tests_count = data.dig('testsCount', '_value') + @tests_failed_count = data.dig('testsFailedCount', '_value') + @tests_skipped_count = data.dig('testsSkippedCount', '_value') + @warning_count = data.dig('warningCount', '_value') end end - # - SortedKeyValueArray - # * Kind: object - # * Properties: - # + storage: [SortedKeyValueArrayPair] class SortedKeyValueArray # @return [Array] storage attr_reader :storage @@ -1310,11 +935,6 @@ def initialize(data) end end - # - SortedKeyValueArrayPair - # * Kind: object - # * Properties: - # + key: String - # + value: SchemaSerializable class SortedKeyValueArrayPair # @return [String] key attr_reader :key @@ -1327,15 +947,10 @@ def initialize(data) end end - # - SourceCodeContext - # * Kind: object - # * Properties: - # + location: SourceCodeLocation? - # + callStack: [SourceCodeFrame] class SourceCodeContext # @return [SourceCodeLocation, nil] location attr_reader :location - # @return [Array] callStack + # @return [Array] call_stack attr_reader :call_stack def initialize(data) @@ -1344,15 +959,10 @@ def initialize(data) end end - # - SourceCodeFrame - # * Kind: object - # * Properties: - # + addressString: String? - # + symbolInfo: SourceCodeSymbolInfo? class SourceCodeFrame - # @return [String, nil] addressString + # @return [String, nil] address_string attr_reader :address_string - # @return [SourceCodeSymbolInfo, nil] symbolInfo + # @return [SourceCodeSymbolInfo, nil] symbol_info attr_reader :symbol_info def initialize(data) @@ -1361,33 +971,22 @@ def initialize(data) end end - # - SourceCodeLocation - # * Kind: object - # * Properties: - # + filePath: String? - # + lineNumber: Int? class SourceCodeLocation - # @return [String, nil] filePath + # @return [String, nil] file_path attr_reader :file_path - # @return [Int, nil] lineNumber + # @return [Int, nil] line_number attr_reader :line_number def initialize(data) @file_path = data.dig('filePath', '_value') if data['filePath'] - @line_number = data.dig('lineNumber', '_value').to_i if data['lineNumber'] + @line_number = data.dig('lineNumber', '_value') if data['lineNumber'] end end - # - SourceCodeSymbolInfo - # * Kind: object - # * Properties: - # + imageName: String? - # + symbolName: String? - # + location: SourceCodeLocation? class SourceCodeSymbolInfo - # @return [String, nil] imageName + # @return [String, nil] image_name attr_reader :image_name - # @return [String, nil] symbolName + # @return [String, nil] symbol_name attr_reader :symbol_name # @return [SourceCodeLocation, nil] location attr_reader :location @@ -1399,42 +998,23 @@ def initialize(data) end end - # - String - # * Kind: value - class String - - def initialize(data) - end - end - - # - TestAssociatedError - # * Kind: object - # * Properties: - # + domain: String? - # + code: Int? - # + userInfo: SortedKeyValueArray? class TestAssociatedError # @return [String, nil] domain attr_reader :domain # @return [Int, nil] code attr_reader :code - # @return [SortedKeyValueArray, nil] userInfo + # @return [SortedKeyValueArray, nil] user_info attr_reader :user_info def initialize(data) @domain = data.dig('domain', '_value') if data['domain'] - @code = data.dig('code', '_value').to_i if data['code'] + @code = data.dig('code', '_value') if data['code'] @user_info = Models.load_class(data.dig('userInfo', '_type', '_name')).new(data.dig('userInfo')) if data['userInfo'] end end - # - TestFailureIssueSummary - # * Supertype: IssueSummary - # * Kind: object - # * Properties: - # + testCaseName: String class TestFailureIssueSummary < IssueSummary - # @return [String] testCaseName + # @return [String] test_case_name attr_reader :test_case_name def initialize(data) @@ -1443,11 +1023,6 @@ def initialize(data) end end - # - TypeDefinition - # * Kind: object - # * Properties: - # + name: String - # + supertype: TypeDefinition? class TypeDefinition # @return [String] name attr_reader :name From 65e6605f238d04cbb190a6f7fee59979af131751 Mon Sep 17 00:00:00 2001 From: ainame Date: Wed, 24 Nov 2021 10:33:15 +0000 Subject: [PATCH 17/24] Fix a bug where primitive values aren't converted --- lib/xcresult/model_generator.rb | 9 ++-- lib/xcresult/models.gen.rb | 90 ++++++++++++++++----------------- 2 files changed, 49 insertions(+), 50 deletions(-) diff --git a/lib/xcresult/model_generator.rb b/lib/xcresult/model_generator.rb index 9ead46e..3869d4d 100644 --- a/lib/xcresult/model_generator.rb +++ b/lib/xcresult/model_generator.rb @@ -43,6 +43,9 @@ module Models OPEN_MODULE sorted_types.each do |type| + # We use Ruby native classes for values, like Date, String, Int, Double, etc.. + next if type.kind == 'value' + type_text = compose_type(type, 2 * 2) file.puts(type_text) file.puts('') @@ -105,9 +108,6 @@ def parse @format.version = @json['version'].values.join('.') @format.signature = @json['signature'] @format.types = @json['types'].map do |type| - # We use Ruby native classes for values, like Date, String, Int, Double, etc.. - next if type['kind'] == 'value' - Type.new( name: type.dig('type', 'name'), supertype: type.dig('type', 'supertype'), @@ -123,7 +123,7 @@ def parse ) end ) - end.compact + end end end @@ -139,7 +139,6 @@ def name_in_snake_case def mapping(kind, variable_name) return "(#{variable_name}.dig('#{name}', '_values') || []).map {|d| #{_mapping(kind, 'd')} }" if type == 'Array' - _mapping(kind, variable_name) + (is_optional ? " if #{variable_name}['#{name}']" : '') end diff --git a/lib/xcresult/models.gen.rb b/lib/xcresult/models.gen.rb index 70b4eeb..c413d2b 100644 --- a/lib/xcresult/models.gen.rb +++ b/lib/xcresult/models.gen.rb @@ -1,5 +1,5 @@ # This is a generated file. Don't modify this directly! -# Last generated at: 2021-11-24 04:47:30 UTC +# Last generated at: 2021-11-24 10:32:37 UTC # # Name: Xcode Result Types # Version: 3.34 @@ -68,12 +68,12 @@ def initialize(data) @identifier = data.dig('identifier', '_value') @is_wireless = data.dig('isWireless', '_value') @cpu_kind = data.dig('cpuKind', '_value') - @cpu_count = data.dig('cpuCount', '_value') if data['cpuCount'] - @cpu_speed_in_mhz = data.dig('cpuSpeedInMHz', '_value') if data['cpuSpeedInMHz'] - @bus_speed_in_mhz = data.dig('busSpeedInMHz', '_value') if data['busSpeedInMHz'] - @ram_size_in_megabytes = data.dig('ramSizeInMegabytes', '_value') if data['ramSizeInMegabytes'] - @physical_cpu_cores_per_package = data.dig('physicalCPUCoresPerPackage', '_value') if data['physicalCPUCoresPerPackage'] - @logical_cpu_cores_per_package = data.dig('logicalCPUCoresPerPackage', '_value') if data['logicalCPUCoresPerPackage'] + @cpu_count = data.dig('cpuCount', '_value').to_i if data['cpuCount'] + @cpu_speed_in_mhz = data.dig('cpuSpeedInMHz', '_value').to_i if data['cpuSpeedInMHz'] + @bus_speed_in_mhz = data.dig('busSpeedInMHz', '_value').to_i if data['busSpeedInMHz'] + @ram_size_in_megabytes = data.dig('ramSizeInMegabytes', '_value').to_i if data['ramSizeInMegabytes'] + @physical_cpu_cores_per_package = data.dig('physicalCPUCoresPerPackage', '_value').to_i if data['physicalCPUCoresPerPackage'] + @logical_cpu_cores_per_package = data.dig('logicalCPUCoresPerPackage', '_value').to_i if data['logicalCPUCoresPerPackage'] @platform_record = Models.load_class(data.dig('platformRecord', '_type', '_name')).new(data.dig('platformRecord')) end end @@ -112,8 +112,8 @@ def initialize(data) @scheme_command_name = data.dig('schemeCommandName', '_value') @scheme_task_name = data.dig('schemeTaskName', '_value') @title = data.dig('title', '_value') if data['title'] - @started_time = data.dig('startedTime', '_value') - @ended_time = data.dig('endedTime', '_value') + @started_time = Time.parse(data.dig('startedTime', '_value')) + @ended_time = Time.parse(data.dig('endedTime', '_value')) @run_destination = Models.load_class(data.dig('runDestination', '_type', '_name')).new(data.dig('runDestination')) @build_result = Models.load_class(data.dig('buildResult', '_type', '_name')).new(data.dig('buildResult')) @action_result = Models.load_class(data.dig('actionResult', '_type', '_name')).new(data.dig('actionResult')) @@ -216,8 +216,8 @@ def initialize(data) @title = data.dig('title', '_value') @activity_type = data.dig('activityType', '_value') @uuid = data.dig('uuid', '_value') - @start = data.dig('start', '_value') if data['start'] - @finish = data.dig('finish', '_value') if data['finish'] + @start = Time.parse(data.dig('start', '_value')) if data['start'] + @finish = Time.parse(data.dig('finish', '_value')) if data['finish'] @attachments = (data.dig('attachments', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } @subactivities = (data.dig('subactivities', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } @failure_summary_ids = (data.dig('failureSummaryIDs', '_values') || []).map {|d| d.dig('failureSummaryIDs', '_value') } @@ -251,13 +251,13 @@ def initialize(data) @uniform_type_identifier = data.dig('uniformTypeIdentifier', '_value') @name = data.dig('name', '_value') if data['name'] @uuid = data.dig('uuid', '_value') if data['uuid'] - @timestamp = data.dig('timestamp', '_value') if data['timestamp'] + @timestamp = Time.parse(data.dig('timestamp', '_value')) if data['timestamp'] @user_info = Models.load_class(data.dig('userInfo', '_type', '_name')).new(data.dig('userInfo')) if data['userInfo'] @lifetime = data.dig('lifetime', '_value') - @in_activity_identifier = data.dig('inActivityIdentifier', '_value') + @in_activity_identifier = data.dig('inActivityIdentifier', '_value').to_i @filename = data.dig('filename', '_value') if data['filename'] @payload_ref = Models.load_class(data.dig('payloadRef', '_type', '_name')).new(data.dig('payloadRef')) if data['payloadRef'] - @payload_size = data.dig('payloadSize', '_value') + @payload_size = data.dig('payloadSize', '_value').to_i end end @@ -317,7 +317,7 @@ class ActionTestFailureSummary def initialize(data) @message = data.dig('message', '_value') if data['message'] @file_name = data.dig('fileName', '_value') - @line_number = data.dig('lineNumber', '_value') + @line_number = data.dig('lineNumber', '_value').to_i @is_performance_failure = data.dig('isPerformanceFailure', '_value') @uuid = data.dig('uuid', '_value') @issue_type = data.dig('issueType', '_value') if data['issueType'] @@ -325,7 +325,7 @@ def initialize(data) @attachments = (data.dig('attachments', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } @associated_error = Models.load_class(data.dig('associatedError', '_type', '_name')).new(data.dig('associatedError')) if data['associatedError'] @source_code_context = Models.load_class(data.dig('sourceCodeContext', '_type', '_name')).new(data.dig('sourceCodeContext')) if data['sourceCodeContext'] - @timestamp = data.dig('timestamp', '_value') if data['timestamp'] + @timestamp = Time.parse(data.dig('timestamp', '_value')) if data['timestamp'] @is_top_level_failure = data.dig('isTopLevelFailure', '_value') end end @@ -356,11 +356,11 @@ class ActionTestMetadata < ActionTestSummaryIdentifiableObject def initialize(data) @test_status = data.dig('testStatus', '_value') - @duration = data.dig('duration', '_value') if data['duration'] + @duration = data.dig('duration', '_value').to_f if data['duration'] @summary_ref = Models.load_class(data.dig('summaryRef', '_type', '_name')).new(data.dig('summaryRef')) if data['summaryRef'] - @performance_metrics_count = data.dig('performanceMetricsCount', '_value') - @failure_summaries_count = data.dig('failureSummariesCount', '_value') - @activity_summaries_count = data.dig('activitySummariesCount', '_value') + @performance_metrics_count = data.dig('performanceMetricsCount', '_value').to_i + @failure_summaries_count = data.dig('failureSummariesCount', '_value').to_i + @activity_summaries_count = data.dig('activitySummariesCount', '_value').to_i super end end @@ -376,7 +376,7 @@ class ActionTestNoticeSummary def initialize(data) @message = data.dig('message', '_value') if data['message'] @file_name = data.dig('fileName', '_value') - @line_number = data.dig('lineNumber', '_value') + @line_number = data.dig('lineNumber', '_value').to_i end end @@ -407,14 +407,14 @@ class ActionTestPerformanceMetricSummary def initialize(data) @display_name = data.dig('displayName', '_value') @unit_of_measurement = data.dig('unitOfMeasurement', '_value') - @measurements = (data.dig('measurements', '_values') || []).map {|d| d.dig('measurements', '_value') } + @measurements = (data.dig('measurements', '_values') || []).map {|d| d.dig('measurements', '_value').to_f } @identifier = data.dig('identifier', '_value') if data['identifier'] @baseline_name = data.dig('baselineName', '_value') if data['baselineName'] - @baseline_average = data.dig('baselineAverage', '_value') if data['baselineAverage'] - @max_percent_regression = data.dig('maxPercentRegression', '_value') if data['maxPercentRegression'] - @max_percent_relative_standard_deviation = data.dig('maxPercentRelativeStandardDeviation', '_value') if data['maxPercentRelativeStandardDeviation'] - @max_regression = data.dig('maxRegression', '_value') if data['maxRegression'] - @max_standard_deviation = data.dig('maxStandardDeviation', '_value') if data['maxStandardDeviation'] + @baseline_average = data.dig('baselineAverage', '_value').to_f if data['baselineAverage'] + @max_percent_regression = data.dig('maxPercentRegression', '_value').to_f if data['maxPercentRegression'] + @max_percent_relative_standard_deviation = data.dig('maxPercentRelativeStandardDeviation', '_value').to_f if data['maxPercentRelativeStandardDeviation'] + @max_regression = data.dig('maxRegression', '_value').to_f if data['maxRegression'] + @max_standard_deviation = data.dig('maxStandardDeviation', '_value').to_f if data['maxStandardDeviation'] @polarity = data.dig('polarity', '_value') if data['polarity'] end end @@ -447,8 +447,8 @@ class ActionTestRepetitionPolicySummary attr_reader :repetition_mode def initialize(data) - @iteration = data.dig('iteration', '_value') if data['iteration'] - @total_iterations = data.dig('totalIterations', '_value') if data['totalIterations'] + @iteration = data.dig('iteration', '_value').to_i if data['iteration'] + @total_iterations = data.dig('totalIterations', '_value').to_i if data['totalIterations'] @repetition_mode = data.dig('repetitionMode', '_value') if data['repetitionMode'] end end @@ -475,7 +475,7 @@ class ActionTestSummary < ActionTestSummaryIdentifiableObject def initialize(data) @test_status = data.dig('testStatus', '_value') - @duration = data.dig('duration', '_value') + @duration = data.dig('duration', '_value').to_f @performance_metrics = (data.dig('performanceMetrics', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } @failure_summaries = (data.dig('failureSummaries', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } @expected_failures = (data.dig('expectedFailures', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } @@ -494,7 +494,7 @@ class ActionTestSummaryGroup < ActionTestSummaryIdentifiableObject attr_reader :subtests def initialize(data) - @duration = data.dig('duration', '_value') + @duration = data.dig('duration', '_value').to_f @subtests = (data.dig('subtests', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } super end @@ -572,7 +572,7 @@ class ActivityLogAnalyzerStep attr_reader :parent_index def initialize(data) - @parent_index = data.dig('parentIndex', '_value') + @parent_index = data.dig('parentIndex', '_value').to_i end end @@ -621,7 +621,7 @@ def initialize(data) @title = data.dig('title', '_value') @location = Models.load_class(data.dig('location', '_type', '_name')).new(data.dig('location')) if data['location'] @description = data.dig('description', '_value') - @call_depth = data.dig('callDepth', '_value') + @call_depth = data.dig('callDepth', '_value').to_i super end end @@ -661,7 +661,7 @@ class ActivityLogAnalyzerResultMessage < ActivityLogMessage def initialize(data) @steps = (data.dig('steps', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } @result_type = data.dig('resultType', '_value') if data['resultType'] - @key_event_index = data.dig('keyEventIndex', '_value') + @key_event_index = data.dig('keyEventIndex', '_value').to_i super end end @@ -694,8 +694,8 @@ class ActivityLogSection def initialize(data) @domain_type = data.dig('domainType', '_value') @title = data.dig('title', '_value') - @start_time = data.dig('startTime', '_value') if data['startTime'] - @duration = data.dig('duration', '_value') + @start_time = Time.parse(data.dig('startTime', '_value')) if data['startTime'] + @duration = data.dig('duration', '_value').to_f @result = data.dig('result', '_value') if data['result'] @location = Models.load_class(data.dig('location', '_type', '_name')).new(data.dig('location')) if data['location'] @subsections = (data.dig('subsections', '_values') || []).map {|d| Models.load_class(d.dig('_type', '_name')).new(d) } @@ -714,7 +714,7 @@ class ActivityLogCommandInvocationSection < ActivityLogSection def initialize(data) @command_details = data.dig('commandDetails', '_value') @emitted_output = data.dig('emittedOutput', '_value') - @exit_code = data.dig('exitCode', '_value') if data['exitCode'] + @exit_code = data.dig('exitCode', '_value').to_i if data['exitCode'] super end end @@ -917,12 +917,12 @@ class ResultMetrics attr_reader :warning_count def initialize(data) - @analyzer_warning_count = data.dig('analyzerWarningCount', '_value') - @error_count = data.dig('errorCount', '_value') - @tests_count = data.dig('testsCount', '_value') - @tests_failed_count = data.dig('testsFailedCount', '_value') - @tests_skipped_count = data.dig('testsSkippedCount', '_value') - @warning_count = data.dig('warningCount', '_value') + @analyzer_warning_count = data.dig('analyzerWarningCount', '_value').to_i + @error_count = data.dig('errorCount', '_value').to_i + @tests_count = data.dig('testsCount', '_value').to_i + @tests_failed_count = data.dig('testsFailedCount', '_value').to_i + @tests_skipped_count = data.dig('testsSkippedCount', '_value').to_i + @warning_count = data.dig('warningCount', '_value').to_i end end @@ -979,7 +979,7 @@ class SourceCodeLocation def initialize(data) @file_path = data.dig('filePath', '_value') if data['filePath'] - @line_number = data.dig('lineNumber', '_value') if data['lineNumber'] + @line_number = data.dig('lineNumber', '_value').to_i if data['lineNumber'] end end @@ -1008,7 +1008,7 @@ class TestAssociatedError def initialize(data) @domain = data.dig('domain', '_value') if data['domain'] - @code = data.dig('code', '_value') if data['code'] + @code = data.dig('code', '_value').to_i if data['code'] @user_info = Models.load_class(data.dig('userInfo', '_type', '_name')).new(data.dig('userInfo')) if data['userInfo'] end end From 910c1a79ab8b58da6029205a168503d044df29b3 Mon Sep 17 00:00:00 2001 From: ainame Date: Fri, 23 Jul 2021 08:40:18 +0900 Subject: [PATCH 18/24] add Reference#load_object to simplify loading --- lib/xcresult/models.rb | 8 ++++++++ lib/xcresult/parser.rb | 8 +------- spec/xcresult_spec.rb | 5 +---- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/lib/xcresult/models.rb b/lib/xcresult/models.rb index db57d2d..45d1ff6 100644 --- a/lib/xcresult/models.rb +++ b/lib/xcresult/models.rb @@ -71,5 +71,13 @@ def failure_message new_message end end + + class Reference + # @param from [String] a file path of .xcresult to load + def load_object(from:) + data = JSON.parse(`xcrun xcresulttool get --path #{from} --id '#{id}' --format json`) + Models.load_class(target_type.name).new(data) + end + end end end diff --git a/lib/xcresult/parser.rb b/lib/xcresult/parser.rb index c064c34..85b97b4 100644 --- a/lib/xcresult/parser.rb +++ b/lib/xcresult/parser.rb @@ -23,16 +23,10 @@ def action_test_plan_summaries test_refs = actions_invocation_record.actions.map do |action| action.action_result.tests_ref end.compact - ids = test_refs.map(&:id) # Maps ids into ActionTestPlanRunSummaries by executing xcresulttool to get JSON # containing specific information for each test summary - @action_test_plan_summaries = ids.map do |id| - raw = execute_cmd("xcrun xcresulttool get --format json --path #{path} --id #{id}") - json = JSON.parse(raw) - XCResult::Models::ActionTestPlanRunSummaries.new(json) - end - + @action_test_plan_summaries = test_refs.map { |ref| ref.load_object(from: path) } @action_test_plan_summaries end diff --git a/spec/xcresult_spec.rb b/spec/xcresult_spec.rb index 5ee3523..cbbcabd 100644 --- a/spec/xcresult_spec.rb +++ b/spec/xcresult_spec.rb @@ -15,16 +15,13 @@ parser = XCResult::Parser.new(path: path) export_xccovreport_paths = parser.export_xccovreports(destination: destination) - expect(export_xccovreport_paths.count).to eq(1) + expect(export_xccovreport_paths.count).to eq(1) end it 'parse test plan summaries' do parser = XCResult::Parser.new(path: path) - expect(parser).to receive(:execute_cmd).with("xcrun xcresulttool get --format json --path #{path} --id #{summary_id}").and_call_original - summaries = parser.action_test_plan_summaries - testable_summary = summaries[0].summaries[0].testable_summaries[0] expect(summaries.count).to eq(1) From 6b560df1d294a5148b67a6340241a1927e505e0b Mon Sep 17 00:00:00 2001 From: ainame Date: Fri, 23 Jul 2021 08:40:56 +0900 Subject: [PATCH 19/24] Add Parser#export_screenshots --- lib/xcresult/parser.rb | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/lib/xcresult/parser.rb b/lib/xcresult/parser.rb index 85b97b4..8e4a089 100644 --- a/lib/xcresult/parser.rb +++ b/lib/xcresult/parser.rb @@ -2,6 +2,7 @@ require 'shellwords' require 'json' +require 'fileutils' module XCResult class Parser @@ -68,6 +69,43 @@ def export_xccovarchives(destination: nil) end end + def export_screenshots(destination: Dir.pwd, by_device: false, by_locale: false) + # Filter non test results; i.e build action + actions = actions_invocation_record.actions.select {|x| x.action_result.tests_ref } + + # Iterate through each action as it represents run destination (testing device) + actions.each do |action| + action_test_plan_run_summaries = action.action_result.tests_ref.load_object(from: path) + action_testable_summaries = action_test_plan_run_summaries.summaries.flat_map(&:testable_summaries) + + # Iterate thorugh each action_testable_summary as it represents testing conditions such as region and language + action_testable_summaries.each do |action_testable_summary| + # Collect all attachments from a testing condition + attachments = action_testable_summary.all_tests + .map(&:summary_ref) + .map { |ref| ref.load_object(from: path) } + .flat_map(&:activity_summaries) + .flat_map(&:subactivities) + .flat_map(&:attachments) + + # Prepare output directory for each tests + locale = [action_testable_summary.test_language, action_testable_summary.test_region].compact.join('_') + locale = 'UNKOWN' if locale.empty? + output_directory = destination + output_directory = File.join(output_directory, action.run_destination.target_device_record.model_name) if by_device + output_directory = File.join(output_directory, locale) if by_locale + FileUtils.mkdir_p(output_directory) unless Dir.exist?(output_directory) + + # Finally exports attachments + attachments.each do |attachment| + output_path = File.join(output_directory, attachment.filename) + cmd = "xcrun xcresulttool export --path #{path} --id '#{attachment.payload_ref.id}' --output-path '#{output_path}' --type file" + execute_cmd(cmd) + end + end + end + end + private def get_result_bundle_json(id: nil) From eceb87bbdd4856b911401cd83848dd7db6c96377 Mon Sep 17 00:00:00 2001 From: ainame Date: Fri, 23 Jul 2021 08:41:18 +0900 Subject: [PATCH 20/24] Add handy standard script; bin/console, bin/setup --- bin/console | 15 +++++++++++++++ bin/setup | 8 ++++++++ 2 files changed, 23 insertions(+) create mode 100755 bin/console create mode 100755 bin/setup diff --git a/bin/console b/bin/console new file mode 100755 index 0000000..20fc5cd --- /dev/null +++ b/bin/console @@ -0,0 +1,15 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "bundler/setup" +require "xcresult" + +# You can add fixtures and/or initialization code here to make experimenting +# with your gem easier. You can also use a different console, if you like. + +# (If you use this, don't forget to add pry to your Gemfile!) +# require "pry" +# Pry.start + +require "irb" +IRB.start(__FILE__) diff --git a/bin/setup b/bin/setup new file mode 100755 index 0000000..dce67d8 --- /dev/null +++ b/bin/setup @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' +set -vx + +bundle install + +# Do any other automated setup that you need to do here From 540384e47be4dc9501df790488966d1f69d0d2ed Mon Sep 17 00:00:00 2001 From: ainame Date: Fri, 23 Jul 2021 09:16:56 +0900 Subject: [PATCH 21/24] Update README.md --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9e7bf02..630a5f3 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Or install it yourself as: - [x] Allow for easy querying of test plan summaires - [x] Allow for easy exporting of `.xccovreport` files -- [ ] Allow for exporting of screenshots +- [x] Allow for exporting of screenshots - [ ] Add full documentation on all classes and methods - [ ] Add more and better explain examples - [ ] Add tests and improved code coverage @@ -47,6 +47,13 @@ parser = XCResult::Parser.new(path: 'YourProject.xcresult') summaries = parser.action_test_plan_summaries ``` +### Export screenshots + +```rb +parser = XCResult::Parser.new(path: 'YourProject.xcresult') +parser = parser.export_screenshots(destination: './screenshots', by_device: true, by_locale: true) +``` + ## Development After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. From 5c49bf7f8f578d5a0f1427cc440078314632876f Mon Sep 17 00:00:00 2001 From: ainame Date: Fri, 23 Jul 2021 10:12:17 +0900 Subject: [PATCH 22/24] add XCResult::ExportOptions --- lib/xcresult/export_options.rb | 33 +++++++++++++++++++++++++++++++++ lib/xcresult/parser.rb | 13 +++++++------ 2 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 lib/xcresult/export_options.rb diff --git a/lib/xcresult/export_options.rb b/lib/xcresult/export_options.rb new file mode 100644 index 0000000..f27a23d --- /dev/null +++ b/lib/xcresult/export_options.rb @@ -0,0 +1,33 @@ +module XCResult + class ExportOptions + AVAILABLE_OPTIONS = %i[destination by_device by_locale].freeze + + def initialize(**options) + raise ':destination option is required' unless options[:destination] + + options.each do |key, value| + raise "Found unknown option #{key} - #{value}." unless AVAILABLE_OPTIONS.include?(key) + end + + @options = options.dup + @destination = options.delete(:destination) + end + + def output_directory(target_device_record:, action_testable_summary:) + output_directory = @destination + + # keep the order of given options so that you can customize output directory path based on your needs + @options.each do |key, value| + if key == :by_device && value == true + output_directory = File.join(output_directory, target_device_record.model_name) + elsif key == :by_locale && value == true + locale = [action_testable_summary.test_language, action_testable_summary.test_region].compact.join('_') + locale = 'UNKOWN' if locale.empty? + output_directory = File.join(output_directory, locale) + end + end + + output_directory + end + end +end diff --git a/lib/xcresult/parser.rb b/lib/xcresult/parser.rb index 8e4a089..674cd45 100644 --- a/lib/xcresult/parser.rb +++ b/lib/xcresult/parser.rb @@ -4,6 +4,8 @@ require 'json' require 'fileutils' +require_relative 'export_options' + module XCResult class Parser attr_accessor :path, :result_bundle_json, :actions_invocation_record @@ -69,7 +71,7 @@ def export_xccovarchives(destination: nil) end end - def export_screenshots(destination: Dir.pwd, by_device: false, by_locale: false) + def export_screenshots(options = XCResult::ExportOptions.new(destination: Dir.pwd)) # Filter non test results; i.e build action actions = actions_invocation_record.actions.select {|x| x.action_result.tests_ref } @@ -89,11 +91,10 @@ def export_screenshots(destination: Dir.pwd, by_device: false, by_locale: false) .flat_map(&:attachments) # Prepare output directory for each tests - locale = [action_testable_summary.test_language, action_testable_summary.test_region].compact.join('_') - locale = 'UNKOWN' if locale.empty? - output_directory = destination - output_directory = File.join(output_directory, action.run_destination.target_device_record.model_name) if by_device - output_directory = File.join(output_directory, locale) if by_locale + output_directory = options.output_directory( + target_device_record: action.run_destination.target_device_record, + action_testable_summary: action_testable_summary + ) FileUtils.mkdir_p(output_directory) unless Dir.exist?(output_directory) # Finally exports attachments From 3808edd55796f0ba12c1355ce707069e7a71d4a7 Mon Sep 17 00:00:00 2001 From: ainame Date: Fri, 23 Jul 2021 10:13:20 +0900 Subject: [PATCH 23/24] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 630a5f3..0182d28 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ summaries = parser.action_test_plan_summaries ```rb parser = XCResult::Parser.new(path: 'YourProject.xcresult') -parser = parser.export_screenshots(destination: './screenshots', by_device: true, by_locale: true) +parser = parser.export_screenshots(XCResult::ExportOptions.new(destination: './screenshots', by_device: true, by_locale: true)) ``` ## Development From a4f5dcbbbba09fcdd6e99135367d1da75d52d9d1 Mon Sep 17 00:00:00 2001 From: ainame Date: Mon, 26 Jul 2021 02:19:14 +0900 Subject: [PATCH 24/24] Add more options --- lib/xcresult/export_options.rb | 29 +++++++++++++++---- lib/xcresult/parser.rb | 52 ++++++++++++++++++---------------- 2 files changed, 52 insertions(+), 29 deletions(-) diff --git a/lib/xcresult/export_options.rb b/lib/xcresult/export_options.rb index f27a23d..329b5ae 100644 --- a/lib/xcresult/export_options.rb +++ b/lib/xcresult/export_options.rb @@ -1,7 +1,17 @@ module XCResult class ExportOptions - AVAILABLE_OPTIONS = %i[destination by_device by_locale].freeze + AVAILABLE_OPTIONS = %i[destination by_device by_locale by_region by_language by_os_version by_test_name].freeze + # A initializer of ExportOptions class. The order of options that are prefixed with `by_` is important. + # `XCResult::Parser#export_screenshots` will create nested directories by the order you give. + # + # @option [String] destination The base directory path of exported items + # @option [Boolean] by_device If true, a nested directory under destination will be made based on device model name + # @option [Boolean] by_locale If true, a nested directory under destination will be made based on locale; i.e. "en_US" + # @option [Boolean] by_region If true, a nested directory under destination will be made based on region + # @option [Boolean] by_language If true, a nested directory under destination will be made based on language + # @option [Boolean] by_os_version If true, a nested directory under destination will be made based on OS version + # @option [Boolean] by_test_name If true, a nested directory under destination will be made based on test name in TestPlan def initialize(**options) raise ':destination option is required' unless options[:destination] @@ -13,17 +23,26 @@ def initialize(**options) @destination = options.delete(:destination) end - def output_directory(target_device_record:, action_testable_summary:) + def output_directory(target_device_record:, action_test_plan_summary:, action_testable_summary:) output_directory = @destination # keep the order of given options so that you can customize output directory path based on your needs - @options.each do |key, value| - if key == :by_device && value == true + @options.select { |key, value| key.to_s.start_with?('by_') && value == true }.each do |key, _| + case key + when :by_device output_directory = File.join(output_directory, target_device_record.model_name) - elsif key == :by_locale && value == true + when :by_language + output_directory = File.join(output_directory, action_testable_summary.test_language) + when :by_region + output_directory = File.join(output_directory, action_testable_summary.test_region) + when :by_locale locale = [action_testable_summary.test_language, action_testable_summary.test_region].compact.join('_') locale = 'UNKOWN' if locale.empty? output_directory = File.join(output_directory, locale) + when :by_os_version + output_directory = File.join(output_directory, target_device_record.operating_system_version) + when :by_test_name + output_directory = File.join(output_directory, action_test_plan_summary.name) end end diff --git a/lib/xcresult/parser.rb b/lib/xcresult/parser.rb index 674cd45..8ddb686 100644 --- a/lib/xcresult/parser.rb +++ b/lib/xcresult/parser.rb @@ -78,30 +78,34 @@ def export_screenshots(options = XCResult::ExportOptions.new(destination: Dir.pw # Iterate through each action as it represents run destination (testing device) actions.each do |action| action_test_plan_run_summaries = action.action_result.tests_ref.load_object(from: path) - action_testable_summaries = action_test_plan_run_summaries.summaries.flat_map(&:testable_summaries) - - # Iterate thorugh each action_testable_summary as it represents testing conditions such as region and language - action_testable_summaries.each do |action_testable_summary| - # Collect all attachments from a testing condition - attachments = action_testable_summary.all_tests - .map(&:summary_ref) - .map { |ref| ref.load_object(from: path) } - .flat_map(&:activity_summaries) - .flat_map(&:subactivities) - .flat_map(&:attachments) - - # Prepare output directory for each tests - output_directory = options.output_directory( - target_device_record: action.run_destination.target_device_record, - action_testable_summary: action_testable_summary - ) - FileUtils.mkdir_p(output_directory) unless Dir.exist?(output_directory) - - # Finally exports attachments - attachments.each do |attachment| - output_path = File.join(output_directory, attachment.filename) - cmd = "xcrun xcresulttool export --path #{path} --id '#{attachment.payload_ref.id}' --output-path '#{output_path}' --type file" - execute_cmd(cmd) + action_test_plan_summaries = action_test_plan_run_summaries.summaries + + # Iterate thorugh each action_test_plan_summaries as it represents test name + action_test_plan_summaries.each do |action_test_plan_summary| + # Iterate thorugh each action_testable_summary as it represents testing conditions such as region and language + action_test_plan_summary.testable_summaries.each do |action_testable_summary| + # Collect all attachments from a testing condition + attachments = action_testable_summary.all_tests + .map(&:summary_ref) + .map { |ref| ref.load_object(from: path) } + .flat_map(&:activity_summaries) + .flat_map(&:subactivities) + .flat_map(&:attachments) + + # Prepare output directory for each tests + output_directory = options.output_directory( + target_device_record: action.run_destination.target_device_record, + action_test_plan_summary: action_test_plan_summary, + action_testable_summary: action_testable_summary + ) + FileUtils.mkdir_p(output_directory) unless Dir.exist?(output_directory) + + # Finally exports attachments + attachments.each do |attachment| + output_path = File.join(output_directory, attachment.filename) + cmd = "xcrun xcresulttool export --path #{path} --id '#{attachment.payload_ref.id}' --output-path '#{output_path}' --type file" + execute_cmd(cmd) + end end end end