class Xcov::Manager

Public Class Methods

new(options) click to toggle source
# File lib/xcov/manager.rb, line 15
def initialize(options)
  # Set command options
  Xcov.config = options

  # Set project options
  FastlaneCore::Project.detect_projects(options)
  Xcov.project = FastlaneCore::Project.new(options)

  # Set ignored files handler
  Xcov.ignore_handler = IgnoreHandler.new

  # Print summary
  FastlaneCore::PrintTable.print_values(config: options, hide_keys: [:slack_url, :coveralls_repo_token], title: "Summary for xcov #{Xcov::VERSION}")
end

Public Instance Methods

parse_xccoverage() click to toggle source
# File lib/xcov/manager.rb, line 42
def parse_xccoverage
  xccoverage_files = []

  # xcresults to parse and export after collecting
  xcresults_to_parse_and_export = []

  # Find .xccoverage file
  # If no xccov direct path, use the old derived data path method
  if xccov_file_direct_paths.nil?
    extension = Xcov.config[:legacy_support] ? "xccoverage" : "xccovreport"
    
    test_logs_path = derived_data_path + "Logs/Test/"
    UI.important("Derived content from #{Dir["#{test_logs_path}/*"]}")
    
    xccoverage_files = Dir["#{test_logs_path}*.#{extension}", "#{test_logs_path}*.xcresult/*/action.#{extension}"]
    if xccoverage_files.empty?
      xcresult_paths = Dir["#{test_logs_path}*.xcresult"]
      xcresult_paths.each do |xcresult_path|
        xcresults_to_parse_and_export << xcresult_path
      end
    end

    unless test_logs_path.directory?
      ErrorHandler.handle_error("XccoverageFileNotFound")
    end
  else
    # Iterate over direct paths and find .xcresult files
    # that need to be processed before getting coverage
    xccov_file_direct_paths.each do |path|
      if File.extname(path) == '.xcresult'
        xcresults_to_parse_and_export << path
      else
        xccoverage_files << path
      end
    end
  end

  # Iterates over xcresults
  # Exports .xccovarchives
  # Exports .xccovreports and collects the paths
  # Merge .xccovreports if multiple exists and return merged report
  unless xcresults_to_parse_and_export.empty?
    xccoverage_files = process_xcresults!(xcresults_to_parse_and_export)
  end

  # Errors if no coverage files were found
  if xccoverage_files.empty?
    ErrorHandler.handle_error("XccoverageFileNotFound")
  end

  # Convert .xccoverage file to json
  ide_foundation_path = Xcov.config[:legacy_support] ? nil : Xcov.config[:ideFoundationPath]
  xccoverage_files = xccoverage_files.sort_by {|filename| File.mtime(filename)}.reverse
  json_report = Xcov::Core::Parser.parse(xccoverage_files.first, Xcov.config[:output_directory], ide_foundation_path)
  ErrorHandler.handle_error("UnableToParseXccoverageFile") if json_report.nil?

  json_report
end
run() click to toggle source
# File lib/xcov/manager.rb, line 30
def run
  # Run xcov
  json_report = parse_xccoverage
  report = generate_xcov_report(json_report)
  validate_report(report)
  submit_to_coveralls(report)
  tmp_dir = File.join(Xcov.config[:output_directory], 'tmp')
  FileUtils.rm_rf(tmp_dir) if File.directory?(tmp_dir)

  json_report
end

Private Instance Methods

derived_data_path() click to toggle source

Auxiliar methods

# File lib/xcov/manager.rb, line 189
def derived_data_path
  # If DerivedData path was supplied, return
  return Pathname.new(Xcov.config[:derived_data_path]) unless Xcov.config[:derived_data_path].nil?

  # Otherwise check project file
  product_builds_path = Pathname.new(Xcov.project.default_build_settings(key: "SYMROOT"))
  return product_builds_path.parent.parent
end
generate_xcov_report(json_report) click to toggle source
# File lib/xcov/manager.rb, line 103
def generate_xcov_report(json_report)
  # Create output path
  output_path = Xcov.config[:output_directory]
  FileUtils.mkdir_p(output_path)

  # Convert report to xcov model objects
  report = Report.map(json_report)

  # Raise exception in case of failure
  ErrorHandler.handle_error("UnableToMapJsonToXcovModel") if report.nil?

  if Xcov.config[:html_report] then
    resources_path = File.join(output_path, "resources")
    FileUtils.mkdir_p(resources_path)

    # Copy images to output resources folder
    Dir[File.join(File.dirname(__FILE__), "../../assets/images/*")].each do |path|
      FileUtils.cp_r(path, resources_path)
    end

    # Copy stylesheets to output resources folder
    Dir[File.join(File.dirname(__FILE__), "../../assets/stylesheets/*")].each do |path|
      FileUtils.cp_r(path, resources_path)
    end

    # Copy javascripts to output resources folder
    Dir[File.join(File.dirname(__FILE__), "../../assets/javascripts/*")].each do |path|
      FileUtils.cp_r(path, resources_path)
    end

    # Create HTML report
    File.open(File.join(output_path, "index.html"), "wb") do |file|
      file.puts report.html_value
    end
  end

  # Create Markdown report
  if Xcov.config[:markdown_report] then
    File.open(File.join(output_path, "report.md"), "wb") do |file|
      file.puts report.markdown_value
    end
  end

  # Create JSON report
  if Xcov.config[:json_report] then
    File.open(File.join(output_path, "report.json"), "wb") do |file|
      file.puts report.json_value.to_json
    end
  end

  # Post result
  SlackPoster.new.run(report)

  # Print output
  table_rows = []
  report.targets.each do |target|
    table_rows << [target.name, target.displayable_coverage]
  end
  puts Terminal::Table.new({
    title: "xcov Coverage Report".green,
    rows: table_rows
  })
  puts ""

  report
end
process_xcresults!(xcresult_paths) click to toggle source
# File lib/xcov/manager.rb, line 208
def process_xcresults!(xcresult_paths)
  output_path = Xcov.config[:output_directory]
  FileUtils.mkdir_p(output_path)
  
  result_path = ""
  index = 0
  
  xcresult_paths.flat_map do |xcresult_path|
    begin
      parser = XCResult::Parser.new(path: xcresult_path)
      
      # Exporting to same directory as xcresult
      tmp_archive_paths = parser.export_xccovarchives(destination: output_path)
      tmp_report_paths = parser.export_xccovreports(destination: output_path)

      # Rename each file with global index
      tmp_report_paths.each_with_index do |item, i|
        File.rename(tmp_archive_paths[i], "#{output_path}/xccovarchive-#{index + i}.xccovarchive")
        File.rename(item, "#{output_path}/xccovreport-#{index + i}.xccovreport")
        index += 1
      end
    rescue
      UI.error("Error occured while exporting xccovreport from xcresult '#{xcresult_path}'")
      UI.error("Make sure you have both Xcode 11 selected and pointing to the correct xcresult file")
      UI.crash!("Failed to export xccovreport from xcresult'")
    end
  end
  
  # Grab paths from the directory instead of parser
  report_paths = Dir["#{output_path}/*.xccovreport"]
  archive_paths = Dir["#{output_path}/*.xccovarchive"]
      
  # Merge coverage reports
  if report_paths.length > 1 then 
    # Creating array of paths for merging
    paths = ""
    for i in 0..report_paths.length
      paths += " #{report_paths[i]} #{archive_paths[i]}"
    end
        
    UI.important("Merging multiple coverage reports with #{paths}") 
    if system ( "xcrun xccov merge --outReport #{output_path}/out.xccovreport --outArchive #{output_path}/out.xccovarchive #{paths}" ) then
      result_path = "#{output_path}/out.xccovreport"
    else
      UI.error("Error occured during merging multiple coverage reports")
    end
  end

  if result_path == "" then
    # Informating user of export paths
    archive_paths.each do |path|
      UI.important("Copying .xccovarchive to #{path}") 
    end
    report_paths.each do |path|
      UI.important("Copying .xccovreport to #{path}") 
    end
        
    # Return array of report_paths if coverage reports were not merged
    return report_paths
  else
    # Return merged xccovreport
    return [result_path]
  end
end
submit_to_coveralls(report) click to toggle source
# File lib/xcov/manager.rb, line 179
def submit_to_coveralls(report)
  if Xcov.config[:disable_coveralls]
    return
  end
  if !Xcov.config[:coveralls_repo_token].nil? || !(Xcov.config[:coveralls_service_name].nil? && Xcov.config[:coveralls_service_job_id].nil?)
    CoverallsHandler.submit(report)
  end
end
validate_report(report) click to toggle source
# File lib/xcov/manager.rb, line 170
def validate_report(report)
  # Raise exception if overall coverage is under threshold
  minimumPercentage = Xcov.config[:minimum_coverage_percentage] / 100
  if minimumPercentage > report.coverage
    error_message = "Actual Code Coverage (#{"%.2f%%" % (report.coverage*100)}) below threshold of #{"%.2f%%" % (minimumPercentage*100)}"
    ErrorHandler.handle_error_with_custom_message("CoverageUnderThreshold", error_message)
  end
end
xccov_file_direct_paths() click to toggle source
# File lib/xcov/manager.rb, line 198
def xccov_file_direct_paths
  # If xccov_file_direct_path was supplied, return
  if Xcov.config[:xccov_file_direct_path].nil?
      return nil
  end

  path = Xcov.config[:xccov_file_direct_path]
  return [Pathname.new(path).to_s]
end