fastlane-plugin-test_center 3.14.1 → 3.14.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 391e38d9af23a2337bcb32634971de2b7fc3813de2c2e8cddf1e20059ca588df
4
- data.tar.gz: e438f5cef4c5c2864da937ea488b19e254277d0bcffe05721d2292271124c892
3
+ metadata.gz: 8d4ae676597a3e9b639c6f31c26e8b1237c821b5923a404a106637bea39c998c
4
+ data.tar.gz: 916b0ec2290cd0f572f48938c1c0860dac107fdee6be21a94d1325ec445f0825
5
5
  SHA512:
6
- metadata.gz: 44fc75715573240e6acb1146146075d5ba5f58f18e19b7fd568933d7749ed24fc513cff9f0806a46a2013c8575cc537592906901708efb7df13d79b86cdc1ce5
7
- data.tar.gz: 568537fbb709f14c3f91fc0880cda63c88ce22470696f8f1cf17281176f635b4a31af4707e081251dfab06ed5ee6631fe78b63b96ebfe68e71d3538a875a1ca8
6
+ metadata.gz: 7cc2b3a851e7e1a50b2d6fd18fa8565cd1f831e98d74589d7d99c42b1b3242c697e48dbe25fadf7e403de51e665db85d6a9aa29781736e0c274afb2f59b01078
7
+ data.tar.gz: 202223ce81f2649ded6613a8d9f831838aaf72653667b68c6edb98c8ca28d9ace86c7f5d88729533a144d82d8efd814caf5dc6a99aa87735eae49c62a7f972af
data/README.md CHANGED
@@ -99,7 +99,7 @@ _fastlane_ is the easiest way to automate beta deployments and releases for your
99
99
  ## Supporters
100
100
 
101
101
  ![Владислав Давыдов](https://avatars1.githubusercontent.com/u/47553334?s=44&u=4691860dba898943b947180b3d28bb85851b0d7c&v=4)
102
- [vdavydovHH](https://github.com/vdavydovHH)
102
+ [vdavydovHH](https://github.com/vdavydovHH) ![amelendezSGY](https://avatars1.githubusercontent.com/u/28657466?s=44&v=4)[amelendezSGY](https://github.com/amelendezSGY)
103
103
 
104
104
  ## License
105
105
 
@@ -44,9 +44,10 @@ module Fastlane
44
44
  html_file_contents = File.read(html_report_filepath)
45
45
  File.open(html_report_filepath, 'w') do |file|
46
46
  html_file_contents.each_line do |line|
47
- m = %r{(<section class="test-detail[^"]*">)(.*(<|>).*)(</section>)}.match(line)
47
+ m = %r{(<section class="test-detail[^"]*">)(.*(<|>|&(?!amp;)).*)(</section>)}.match(line)
48
48
  if m
49
49
  test_details = m[2]
50
+ test_details.gsub!(/&(?!amp;)/, '&amp;')
50
51
  test_details.gsub!('<', '&lt;')
51
52
  test_details.gsub!('>', '&gt;')
52
53
  line = m[1] + test_details + m[4]
@@ -1,6 +1,7 @@
1
1
  module Fastlane
2
2
  module Actions
3
3
  class CollateJunitReportsAction < Action
4
+ require 'set'
4
5
 
5
6
  def self.run(params)
6
7
  report_filepaths = params[:reports]
@@ -9,17 +10,27 @@ module Fastlane
9
10
  else
10
11
  UI.verbose("collate_junit_reports with #{report_filepaths}")
11
12
  reports = report_filepaths.map { |report_filepath| REXML::Document.new(File.new(report_filepath)) }
13
+ packages = reports.map { |r| r.root.attribute('name').value }.uniq
14
+ combine_multiple_targets = packages.size > 1
15
+
12
16
  # copy any missing testsuites
13
17
  target_report = reports.shift
14
- preprocess_testsuites(target_report)
18
+
19
+ package = target_report.root.attribute('name').value
20
+ preprocess_testsuites(target_report, package, combine_multiple_targets)
15
21
 
16
22
  reports.each do |report|
17
23
  increment_testable_tries(target_report.root, report.root)
18
- preprocess_testsuites(report)
24
+ package = report.root.attribute('name').value
25
+ preprocess_testsuites(report, package, combine_multiple_targets)
19
26
  UI.verbose("> collating last report file #{report_filepaths.last}")
20
27
  report.elements.each('//testsuite') do |testsuite|
21
28
  testsuite_name = testsuite.attribute('name').value
22
- target_testsuite = REXML::XPath.first(target_report, "//testsuite[@name='#{testsuite_name}']")
29
+ package_attribute = ''
30
+ if combine_multiple_targets
31
+ package_attribute = "@package='#{package}'"
32
+ end
33
+ target_testsuite = REXML::XPath.first(target_report, "//testsuite[@name='#{testsuite_name}' #{package_attribute}]")
23
34
  if target_testsuite
24
35
  UI.verbose(" > collating testsuite #{testsuite_name}")
25
36
  collate_testsuite(target_testsuite, testsuite)
@@ -36,6 +47,7 @@ module Fastlane
36
47
  end
37
48
  testable = REXML::XPath.first(target_report, 'testsuites')
38
49
  update_testable_counts(testable)
50
+ testable.add_attribute('name', packages.to_a.join(', '))
39
51
 
40
52
  FileUtils.mkdir_p(File.dirname(params[:collated_report]))
41
53
  File.open(params[:collated_report], 'w') do |f|
@@ -81,10 +93,11 @@ module Fastlane
81
93
  update_testsuite_counts(testsuite)
82
94
  end
83
95
 
84
- def self.preprocess_testsuites(report)
96
+ def self.preprocess_testsuites(report, package, combine_multiple_targets)
85
97
  report.elements.each('//testsuite') do |testsuite|
86
98
  flatten_duplicate_testsuites(report, testsuite)
87
99
  collapse_testcase_multiple_failures_in_testsuite(testsuite)
100
+ testsuite.add_attribute('package', package) if combine_multiple_targets
88
101
  end
89
102
  end
90
103
 
@@ -221,10 +221,12 @@ module Fastlane
221
221
  'collate the test_result bundles to a temporary bundle \"result.test_result\"'
222
222
  )
223
223
  bundles = Dir['../spec/fixtures/*.test_result'].map { |relpath| File.absolute_path(relpath) }
224
- collate_test_result_bundles(
225
- bundles: bundles,
226
- collated_bundle: File.join('test_output', 'result.test_result')
227
- )
224
+ Dir.mktmpdir('test_output') do |dir|
225
+ collate_test_result_bundles(
226
+ bundles: bundles,
227
+ collated_bundle: File.join(dir, 'result.test_result')
228
+ )
229
+ end
228
230
  "
229
231
  ]
230
232
  end
@@ -86,15 +86,19 @@ module Fastlane
86
86
  def self.example_code
87
87
  [
88
88
  "
89
+ require 'tmpdir'
90
+
89
91
  UI.important(
90
92
  'example: ' \\
91
93
  'collate the xcresult bundles to a temporary xcresult bundle \"result.xcresult\"'
92
94
  )
93
- xcresults = Dir['../spec/fixtures/*.xcresult'].map { |relpath| File.absolute_path(relpath) }
94
- collate_xcresults(
95
- xcresults: xcresults,
96
- collated_xcresult: File.join('test_output', 'result.xcresult')
97
- )
95
+ xcresults = Dir['../spec/fixtures/AtomicBoyUITests-batch-{3,4}/result.xcresult'].map { |relpath| File.absolute_path(relpath) }
96
+ Dir.mktmpdir('test_output') do |dir|
97
+ collate_xcresults(
98
+ xcresults: xcresults,
99
+ collated_xcresult: File.join(dir, 'result.xcresult')
100
+ )
101
+ end
98
102
  "
99
103
  ]
100
104
  end
@@ -28,16 +28,23 @@ module Fastlane
28
28
  force_quit_simulator_processes if params[:quit_simulators]
29
29
 
30
30
  prepare_for_testing(params.values)
31
+
32
+ tests_passed = true
33
+ summary = {}
34
+ if params[:build_for_testing]
35
+ summary = build_summary
36
+ else
37
+ coerce_destination_to_array(params)
38
+ platform = :mac
39
+ platform = :ios_simulator if Scan.config[:destination].any? { |d| d.include?('platform=iOS Simulator') }
31
40
 
32
- coerce_destination_to_array(params)
33
- platform = :mac
34
- platform = :ios_simulator if Scan.config[:destination].any? { |d| d.include?('platform=iOS Simulator') }
41
+ runner_options = params.values.merge(platform: platform)
42
+ runner = ::TestCenter::Helper::MultiScanManager::Runner.new(runner_options)
43
+ tests_passed = runner.run
35
44
 
36
- runner_options = params.values.merge(platform: platform)
37
- runner = ::TestCenter::Helper::MultiScanManager::Runner.new(runner_options)
38
- tests_passed = runner.run
45
+ summary = run_summary(params, tests_passed)
46
+ end
39
47
 
40
- summary = run_summary(params, tests_passed)
41
48
  print_run_summary(summary)
42
49
 
43
50
  if params[:fail_build] && !tests_passed
@@ -101,6 +108,16 @@ module Fastlane
101
108
  # :nocov:
102
109
  end
103
110
 
111
+ def self.build_summary
112
+ {
113
+ result: true,
114
+ total_tests: 0,
115
+ passing_testcount: 0,
116
+ failed_testcount: 0,
117
+ total_retry_count: 0
118
+ }
119
+ end
120
+
104
121
  def self.run_summary(scan_options, tests_passed)
105
122
  scan_options = scan_options.clone
106
123
 
@@ -171,7 +188,10 @@ module Fastlane
171
188
  use_scanfile_to_override_settings(scan_options)
172
189
  turn_off_concurrent_workers(scan_options)
173
190
  UI.important("Turning off :skip_build as it doesn't do anything with multi_scan") if scan_options[:skip_build]
174
- scan_options.reject! { |k,v| k == :skip_build }
191
+ if scan_options[:disable_xcpretty]
192
+ UI.important("Turning off :disable_xcpretty as xcpretty is needed to generate junit reports for retrying failed tests")
193
+ end
194
+ scan_options.reject! { |k,v| %i[skip_build disable_xcpretty].include?(k) }
175
195
  ScanHelper.remove_preexisting_simulator_logs(scan_options)
176
196
  if scan_options[:test_without_building]
177
197
  UI.verbose("Preparing Scan config options for multi_scan testing")
@@ -358,6 +378,14 @@ module Fastlane
358
378
  UI.user_error!("Error: Batch counts must be greater than zero") unless count > 0
359
379
  end
360
380
  ),
381
+ FastlaneCore::ConfigItem.new(
382
+ key: :batches,
383
+ env_name: "FL_MULTI_SCAN_BATCHES",
384
+ description: "The explicit batches (an Array of Arrays of test identifiers) to run either serially, or each batch on a simulator in parallel if :parallel_testrun_count is given",
385
+ type: Array,
386
+ optional: true,
387
+ conflicting_options: [:batch_count]
388
+ ),
361
389
  FastlaneCore::ConfigItem.new(
362
390
  key: :retry_test_runner_failures,
363
391
  description: "Set to true If you want to treat build failures during testing, like 'Test runner exited before starting test execution', as 'all tests failed'",
@@ -372,7 +400,7 @@ module Fastlane
372
400
  is_string: false,
373
401
  default_value: false,
374
402
  optional: true,
375
- conflicting_options: [:batch_count],
403
+ conflicting_options: %i[batch_count batches],
376
404
  conflict_block: proc do |value|
377
405
  UI.user_error!(
378
406
  "Error: Can't use 'invocation_based_tests' and 'batch_count' options in one run, "\
@@ -430,7 +458,7 @@ module Fastlane
430
458
  ),
431
459
  FastlaneCore::ConfigItem.new(
432
460
  key: :testrun_completed_block,
433
- description: 'A block invoked each time a test run completes. When combined with :parallel_testrun_count, will be called separately in each child process',
461
+ description: 'A block invoked each time a test run completes. When combined with :parallel_testrun_count, will be called separately in each child process. Return a Hash with :continue set to false to stop retrying tests, or :only_testing to change which tests will be run in the next try',
434
462
  optional: true,
435
463
  is_string: false,
436
464
  default_value: nil,
@@ -458,6 +486,10 @@ module Fastlane
458
486
 
459
487
  # UI.abort_with_message!('You could conditionally abort')
460
488
  UI.message(\"\\\u1F60A everything is fine, let's continue try \#{try_attempt + 1} for batch \#{batch}\")
489
+ {
490
+ continue: true,
491
+ only_testing: ['AtomicBoyUITests/AtomicBoyUITests/testExample17']
492
+ }
461
493
  end
462
494
 
463
495
  multi_scan(
@@ -483,6 +515,199 @@ module Fastlane
483
515
  output_files: 'report.json',
484
516
  fail_build: false
485
517
  )
518
+ ",
519
+ "
520
+ UI.header('batches feature')
521
+ multi_scan(
522
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
523
+ scheme: 'AtomicBoy',
524
+ try_count: 3,
525
+ fail_build: false,
526
+ batches: [
527
+ [
528
+ 'AtomicBoyUITests/AtomicBoyUITests/testExample5',
529
+ 'AtomicBoyUITests/AtomicBoyUITests/testExample10',
530
+ 'AtomicBoyUITests/AtomicBoyUITests/testExample15'
531
+ ],
532
+ [
533
+ 'AtomicBoyUITests/AtomicBoyUITests/testExample6',
534
+ 'AtomicBoyUITests/AtomicBoyUITests/testExample12',
535
+ 'AtomicBoyUITests/AtomicBoyUITests/testExample18'
536
+ ]
537
+ ]
538
+ )
539
+ "
540
+ ]
541
+ end
542
+
543
+ def self.integration_tests
544
+ [
545
+ "
546
+ UI.header('Basic test')
547
+ multi_scan(
548
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
549
+ scheme: 'AtomicBoy',
550
+ fail_build: false,
551
+ try_count: 2,
552
+ disable_xcpretty: true
553
+ )
554
+ ",
555
+ "
556
+ UI.header('Basic test with 1 specific test')
557
+ multi_scan(
558
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
559
+ scheme: 'AtomicBoy',
560
+ fail_build: false,
561
+ try_count: 2,
562
+ only_testing: ['AtomicBoyUITests/AtomicBoyUITests/testExample']
563
+ )
564
+ ",
565
+ "
566
+ UI.header('Basic test with test target expansion')
567
+ multi_scan(
568
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
569
+ scheme: 'AtomicBoy',
570
+ fail_build: false,
571
+ try_count: 2,
572
+ only_testing: ['AtomicBoyUITests', 'AtomicBoyTests']
573
+ )
574
+ ",
575
+ "
576
+ UI.header('Parallel test run')
577
+ multi_scan(
578
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
579
+ scheme: 'AtomicBoy',
580
+ fail_build: false,
581
+ try_count: 2,
582
+ parallel_testrun_count: 2
583
+ )
584
+ ",
585
+ "
586
+ UI.header('Parallel test run with fewer tests than parallel test runs')
587
+ multi_scan(
588
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
589
+ scheme: 'AtomicBoy',
590
+ fail_build: false,
591
+ try_count: 2,
592
+ parallel_testrun_count: 4,
593
+ only_testing: ['AtomicBoyUITests/AtomicBoyUITests/testExample']
594
+ )
595
+ ",
596
+ "
597
+ UI.header('Basic test with batch count')
598
+ multi_scan(
599
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
600
+ scheme: 'AtomicBoy',
601
+ fail_build: false,
602
+ try_count: 2,
603
+ batch_count: 2
604
+ )
605
+ ",
606
+ "
607
+ UI.header('Basic test with batches')
608
+ multi_scan(
609
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
610
+ scheme: 'AtomicBoy',
611
+ fail_build: false,
612
+ try_count: 2,
613
+ batches: [
614
+ ['AtomicBoyUITests/AtomicBoyUITests/testExample', 'AtomicBoyUITests/AtomicBoyUITests/testExample2'],
615
+ ['AtomicBoyUITests/AtomicBoyUITests/testExample3', 'AtomicBoyUITests/AtomicBoyUITests/testExample4']
616
+ ],
617
+ parallel_testrun_count: 2
618
+ )
619
+ ",
620
+ "
621
+ UI.header('Basic test with xcresult')
622
+ multi_scan(
623
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
624
+ scheme: 'AtomicBoy',
625
+ output_types: 'xcresult',
626
+ output_files: 'result.xcresult',
627
+ collate_reports: false,
628
+ fail_build: false,
629
+ try_count: 2,
630
+ batch_count: 2
631
+ )
632
+ "
633
+ ]
634
+ end
635
+
636
+ def self.integration_tests
637
+ [
638
+ "
639
+ UI.header('Basic test')
640
+ multi_scan(
641
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
642
+ scheme: 'AtomicBoy',
643
+ fail_build: false,
644
+ try_count: 2,
645
+ disable_xcpretty: true
646
+ )
647
+ ",
648
+ "
649
+ UI.header('Basic test with 1 specific test')
650
+ multi_scan(
651
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
652
+ scheme: 'AtomicBoy',
653
+ fail_build: false,
654
+ try_count: 2,
655
+ only_testing: ['AtomicBoyUITests/AtomicBoyUITests/testExample']
656
+ )
657
+ ",
658
+ "
659
+ UI.header('Basic test with test target expansion')
660
+ multi_scan(
661
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
662
+ scheme: 'AtomicBoy',
663
+ fail_build: false,
664
+ try_count: 2,
665
+ only_testing: ['AtomicBoyUITests', 'AtomicBoyTests']
666
+ )
667
+ ",
668
+ "
669
+ UI.header('Parallel test run')
670
+ multi_scan(
671
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
672
+ scheme: 'AtomicBoy',
673
+ fail_build: false,
674
+ try_count: 2,
675
+ parallel_testrun_count: 2
676
+ )
677
+ ",
678
+ "
679
+ UI.header('Parallel test run with fewer tests than parallel test runs')
680
+ multi_scan(
681
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
682
+ scheme: 'AtomicBoy',
683
+ fail_build: false,
684
+ try_count: 2,
685
+ parallel_testrun_count: 4,
686
+ only_testing: ['AtomicBoyUITests/AtomicBoyUITests/testExample']
687
+ )
688
+ ",
689
+ "
690
+ UI.header('Basic test with batches')
691
+ multi_scan(
692
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
693
+ scheme: 'AtomicBoy',
694
+ fail_build: false,
695
+ try_count: 2,
696
+ batch_count: 2
697
+ )
698
+ ",
699
+ "
700
+ UI.header('Basic test with xcresult')
701
+ multi_scan(
702
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
703
+ scheme: 'AtomicBoy',
704
+ output_types: 'xcresult',
705
+ output_files: 'result.xcresult',
706
+ collate_reports: false,
707
+ fail_build: false,
708
+ try_count: 2,
709
+ batch_count: 2
710
+ )
486
711
  "
487
712
  ]
488
713
  end
@@ -0,0 +1,103 @@
1
+ require 'trainer'
2
+ require 'shellwords'
3
+
4
+ module Fastlane
5
+ module Actions
6
+ class TestsFromXcresultAction < Action
7
+ def self.run(params)
8
+ unless FastlaneCore::Helper.xcode_at_least?('11.0.0')
9
+ UI.error("Error: tests_from_xcresult requires at least Xcode 11.0")
10
+ return {}
11
+ end
12
+
13
+ xcresult_path = File.absolute_path(params[:xcresult])
14
+
15
+ # taken from the rubygem trainer, in the test_parser.rb module
16
+ result_bundle_object_raw = sh("xcrun xcresulttool get --path #{xcresult_path.shellescape} --format json", print_command: false, print_command_output: false)
17
+ result_bundle_object = JSON.parse(result_bundle_object_raw)
18
+
19
+ # Parses JSON into ActionsInvocationRecord to find a list of all ids for ActionTestPlanRunSummaries
20
+ actions_invocation_record = Trainer::XCResult::ActionsInvocationRecord.new(result_bundle_object)
21
+ test_refs = actions_invocation_record.actions.map do |action|
22
+ action.action_result.tests_ref
23
+ end.compact
24
+
25
+ ids = test_refs.map(&:id)
26
+ summaries = ids.map do |id|
27
+ raw = sh("xcrun xcresulttool get --format json --path #{xcresult_path.shellescape} --id #{id}", print_command: false, print_command_output: false)
28
+ json = JSON.parse(raw)
29
+ Trainer::XCResult::ActionTestPlanRunSummaries.new(json)
30
+ end
31
+ failures = actions_invocation_record.issues.test_failure_summaries || []
32
+ all_summaries = summaries.map(&:summaries).flatten
33
+ testable_summaries = all_summaries.map(&:testable_summaries).flatten
34
+ failed = []
35
+ passing = []
36
+ failure_details = {}
37
+ rows = testable_summaries.map do |testable_summary|
38
+ all_tests = testable_summary.all_tests.flatten
39
+ all_tests.each do |t|
40
+ if t.test_status == 'Success'
41
+ passing << "#{t.parent.name}/#{t.identifier}"
42
+ else
43
+ test_identifier = "#{t.parent.name}/#{t.identifier}"
44
+ failed << test_identifier
45
+ failure = t.find_failure(failures)
46
+ if failure
47
+ failure_details[test_identifier] = {
48
+ message: failure.failure_message
49
+ }
50
+ end
51
+ end
52
+ end
53
+ end
54
+ {
55
+ failed: failed.uniq,
56
+ passing: passing.uniq,
57
+ failure_details: failure_details
58
+ }
59
+ end
60
+
61
+ #####################################################
62
+ # @!group Documentation
63
+ #####################################################
64
+
65
+ def self.description
66
+ "☑️ Retrieves the failing and passing tests as reportedn an xcresult bundle"
67
+ end
68
+
69
+
70
+ def self.available_options
71
+ [
72
+ FastlaneCore::ConfigItem.new(
73
+ key: :xcresult,
74
+ env_name: "FL_TESTS_FROM_XCRESULT_XCRESULT_PATH",
75
+ description: "The path to the xcresult bundle to retrieve the tests from",
76
+ verify_block: proc do |path|
77
+ UI.user_error!("Error: cannot find the xcresult bundle at '#{path}'") unless Dir.exist?(path)
78
+ UI.user_error!("Error: cannot parse files that are not in the xcresult format") unless File.extname(path) == ".xcresult"
79
+ end
80
+ )
81
+ ]
82
+ end
83
+
84
+ def self.return_value
85
+ "A Hash with information about the test results:\r\n" \
86
+ "failed: an Array of the failed test identifiers\r\n" \
87
+ "passing: an Array of the passing test identifiers\r\n"
88
+ end
89
+
90
+ def self.authors
91
+ ["lyndsey-ferguson/lyndseydf"]
92
+ end
93
+
94
+ def self.category
95
+ :testing
96
+ end
97
+
98
+ def self.is_supported?(platform)
99
+ %i[ios mac].include?(platform)
100
+ end
101
+ end
102
+ end
103
+ end
@@ -1,6 +1,7 @@
1
1
 
2
2
  module FixedCopyLogarchiveFastlaneSimulator
3
3
  def self.included(base)
4
+ @@log_collection_start_time = DateTime.now
4
5
  base.instance_eval do
5
6
  def copy_logarchive(device, log_identity, logs_destination_dir)
6
7
  require 'shellwords'
@@ -8,7 +9,11 @@ module FixedCopyLogarchiveFastlaneSimulator
8
9
  logarchive_dst = File.join(logs_destination_dir, "system_logs-#{log_identity}.logarchive")
9
10
  FileUtils.rm_rf(logarchive_dst)
10
11
  FileUtils.mkdir_p(File.expand_path("..", logarchive_dst))
11
- command = "xcrun simctl spawn #{device.udid} log collect --output #{logarchive_dst.shellescape} 2>/dev/null"
12
+
13
+ logs_collection_start = @@log_collection_start_time.strftime('%Y-%m-%d %H:%M:%S')
14
+ command = "xcrun simctl spawn #{device.udid} log collect "
15
+ command << "--start '#{logs_collection_start}' "
16
+ command << "--output #{logarchive_dst.shellescape} 2>/dev/null"
12
17
  FastlaneCore::CommandExecutor.execute(command: command, print_all: false, print_command: true)
13
18
  end
14
19
  end
@@ -16,6 +16,7 @@ module TestCenter
16
16
  @options[:output_files],
17
17
  @options[:custom_report_file_name]
18
18
  )
19
+ @callback_overrides_only_testing = false
19
20
  end
20
21
 
21
22
  def before_testrun
@@ -189,7 +190,26 @@ module TestCenter
189
190
  update_json_failure_details(info)
190
191
  update_test_result_bundle_details(info)
191
192
 
192
- @options[:testrun_completed_block].call(info)
193
+ @callback_overrides_only_testing = false
194
+ callback_result = @options[:testrun_completed_block].call(info)
195
+ if callback_result.kind_of?(Hash)
196
+ should_continue = callback_result.fetch(:continue, true)
197
+ if !should_continue
198
+ discontinue_message = 'Following testrun_completed_block\'s request to discontinue testing'
199
+ discontinue_message << " for batch ##{@options[:batch]}" unless @options[:batch].nil?
200
+ FastlaneCore::UI.verbose(discontinue_message)
201
+ @testrun_count = options[:try_count]
202
+ end
203
+ overridden_only_testing = callback_result.fetch(:only_testing, nil)
204
+ if overridden_only_testing && should_continue
205
+ override_only_testing_message = 'Following testrun_completed_block\'s request to change :only_testing to '
206
+ override_only_testing_message << overridden_only_testing.to_s
207
+ override_only_testing_message << " for batch ##{@options[:batch]}" unless @options[:batch].nil?
208
+ FastlaneCore::UI.verbose(override_only_testing_message)
209
+ @callback_overrides_only_testing = true
210
+ @options[:only_testing] = overridden_only_testing
211
+ end
212
+ end
193
213
  end
194
214
 
195
215
  def failure_details(additional_info)
@@ -242,6 +262,8 @@ module TestCenter
242
262
  end
243
263
 
244
264
  def update_only_testing
265
+ return if @callback_overrides_only_testing
266
+
245
267
  report_filepath = File.join(output_directory, @reportnamer.junit_last_reportname)
246
268
  config = FastlaneCore::Configuration.create(
247
269
  Fastlane::Actions::TestsFromJunitAction.available_options,
@@ -41,6 +41,7 @@ module TestCenter
41
41
  def setup_logcollection
42
42
  FastlaneCore::UI.verbose("> setup_logcollection")
43
43
  return unless @options[:include_simulator_logs]
44
+ return unless @options[:platform] == :ios_simulator
44
45
  return if Scan::Runner.method_defined?(:prelaunch_simulators)
45
46
 
46
47
  # We need to prelaunch the simulators so xcodebuild
@@ -65,8 +66,8 @@ module TestCenter
65
66
 
66
67
  @test_collector = TestCollector.new(@options)
67
68
  @options.reject! { |key| %i[testplan].include?(key) }
68
- @batch_count = @test_collector.test_batches.size
69
- tests = @test_collector.test_batches.flatten
69
+ @batch_count = @test_collector.batches.size
70
+ tests = @test_collector.batches.flatten
70
71
  if tests.size < @options[:parallel_testrun_count].to_i
71
72
  FastlaneCore::UI.important(":parallel_testrun_count greater than the number of tests (#{tests.size}). Reducing to that number.")
72
73
  @options[:parallel_testrun_count] = tests.size
@@ -89,16 +90,51 @@ module TestCenter
89
90
  remove_preexisting_test_result_bundles
90
91
  remote_preexisting_xcresult_bundles
91
92
 
92
- tests_passed = false
93
+ test_results = [false]
93
94
  if should_run_tests_through_single_try?
94
- tests_passed = run_tests_through_single_try
95
+ test_results.clear
96
+ setup_run_tests_for_each_device do |device_name|
97
+ FastlaneCore::UI.message("Single try testing for device '#{device_name}'") if device_name
98
+ test_results << run_tests_through_single_try
99
+ end
95
100
  end
96
101
 
97
- unless tests_passed || @options[:try_count] < 1
102
+ unless test_results.all? || @options[:try_count] < 1
103
+ test_results.clear
98
104
  setup_testcollector
99
- tests_passed = run_test_batches
105
+ setup_run_tests_for_each_device do |device_name|
106
+ FastlaneCore::UI.message("Testing batches for device '#{device_name}'") if device_name
107
+ test_results << run_test_batches
108
+ end
100
109
  end
101
- tests_passed
110
+ test_results.all?
111
+ end
112
+
113
+ def setup_run_tests_for_each_device
114
+ original_output_directory = @options.fetch(:output_directory, 'test_results')
115
+ unless @options[:platform] == :ios_simulator
116
+ yield
117
+ return
118
+ end
119
+
120
+ scan_destinations = Scan.config[:destination].clone
121
+ try_count = @options[:try_count]
122
+
123
+ scan_destinations.each_with_index do |destination, device_index|
124
+ @options[:try_count] = try_count
125
+ device_udid_match = destination.match(/id=(?<udid>[^,]+)/)
126
+ device_udid = device_udid_match[:udid] if device_udid_match
127
+ if scan_destinations.size > 1
128
+ @options[:output_directory] = File.join(original_output_directory, device_udid)
129
+ Scan.config[:destination].replace([destination])
130
+ end
131
+ command = "xcrun simctl list devices | grep #{device_udid}"
132
+ device_info = Fastlane::Actions.sh(command, log: false)
133
+
134
+ yield device_info.strip.gsub(/ \(#{device_udid}.*/, '')
135
+ end
136
+ Scan.config[:destination].replace(scan_destinations)
137
+ @options[:output_directory] = original_output_directory
102
138
  end
103
139
 
104
140
  def should_run_tests_through_single_try?
@@ -196,7 +232,7 @@ module TestCenter
196
232
  pool = TestBatchWorkerPool.new(pool_options)
197
233
  pool.setup_workers
198
234
 
199
- remaining_test_batches = @test_collector.test_batches.clone
235
+ remaining_test_batches = @test_collector.batches.clone
200
236
  remaining_test_batches.each_with_index do |test_batch, current_batch_index|
201
237
  worker = pool.wait_for_worker
202
238
  FastlaneCore::UI.message("Starting test run #{current_batch_index + 1}")
@@ -209,7 +245,7 @@ module TestCenter
209
245
  end
210
246
 
211
247
  def scan_options_for_worker(test_batch, batch_index)
212
- if @test_collector.test_batches.size > 1
248
+ if @test_collector.batches.size > 1
213
249
  # If there are more than 1 batch, then we want each batch result
214
250
  # sent to a "batch index" output folder to be collated later
215
251
  # into the requested output_folder.
@@ -234,6 +270,7 @@ module TestCenter
234
270
  @test_collector.testables.each do |testable|
235
271
  collate_batched_reports_for_testable(testable)
236
272
  end
273
+ collate_multitarget_junits
237
274
  move_single_testable_reports_to_final_location
238
275
  end
239
276
 
@@ -295,6 +332,31 @@ module TestCenter
295
332
  File.symlink(xcresult_bundlename_path, test_result_bundlename_path)
296
333
  end
297
334
 
335
+ def collate_multitarget_junits
336
+ return if @test_collector.testables.size < 2
337
+
338
+ Fastlane::UI.verbose("Collating test targets's junit results")
339
+
340
+ given_custom_report_file_name = @options[:custom_report_file_name]
341
+ given_output_types = @options[:output_types]
342
+ given_output_files = @options[:output_files]
343
+
344
+ report_name_helper = ReportNameHelper.new(
345
+ given_output_types,
346
+ given_output_files,
347
+ given_custom_report_file_name
348
+ )
349
+
350
+ absolute_output_directory = File.absolute_path(output_directory)
351
+ source_reports_directory_glob = "#{absolute_output_directory}/*"
352
+
353
+ TestCenter::Helper::MultiScanManager::ReportCollator.new(
354
+ source_reports_directory_glob: source_reports_directory_glob,
355
+ output_directory: absolute_output_directory,
356
+ reportnamer: report_name_helper
357
+ ).collate_junit_reports
358
+ end
359
+
298
360
  def collate_batched_reports_for_testable(testable)
299
361
  FastlaneCore::UI.verbose("Collating results for all batches")
300
362
 
@@ -63,7 +63,9 @@ module TestCenter
63
63
  def parallel_scan_options(worker_index)
64
64
  options = @options.reject { |key| %i[device devices].include?(key) }
65
65
  options[:destination] = destination_for_worker(worker_index)
66
- options[:scan_devices_override] = simulator_devices_for_worker(worker_index)
66
+ if @options[:platform] == :ios_simulator
67
+ options[:scan_devices_override] = simulator_devices_for_worker(worker_index)
68
+ end
67
69
  options[:buildlog_path] = buildlog_path_for_worker(worker_index) if @options[:buildlog_path]
68
70
  options[:derived_data_path] = derived_data_path_for_worker(worker_index)
69
71
  options[:batch_index] = worker_index
@@ -89,6 +91,12 @@ module TestCenter
89
91
  clones.flatten.each(&:delete)
90
92
  end
91
93
 
94
+ def shutdown_cloned_simulators(clones)
95
+ return if clones.nil?
96
+
97
+ clones.flatten.each(&:shutdown)
98
+ end
99
+
92
100
  def setup_serial_workers
93
101
  serial_scan_options = @options.reject { |key| %i[device devices].include?(key) }
94
102
  serial_scan_options[:destination] ||= Scan&.config&.fetch(:destination)
@@ -125,6 +133,7 @@ module TestCenter
125
133
  busy_workers.map(&:pid).each do |pid|
126
134
  Process.wait(pid)
127
135
  end
136
+ shutdown_cloned_simulators(@clones)
128
137
  busy_workers.each { |w| w.process_results }
129
138
  end
130
139
  end
@@ -3,34 +3,154 @@ module TestCenter
3
3
  require 'fastlane_core/ui/ui.rb'
4
4
  require 'fastlane/actions/scan'
5
5
  require 'plist'
6
+ require 'set'
6
7
 
7
8
  class TestCollector
8
9
  attr_reader :xctestrun_path
9
- attr_reader :only_testing
10
+ attr_reader :batches
11
+ attr_reader :testables
10
12
 
11
13
  def initialize(options)
12
- unless options[:xctestrun] || options[:derived_data_path]
13
- options[:derived_data_path] = default_derived_data_path(options)
14
+ @invocation_based_tests = options[:invocation_based_tests]
15
+ @swift_test_prefix = options[:swift_test_prefix]
16
+
17
+ @xctestrun_path = self.class.xctestrun_filepath(options)
18
+ initialize_batches(options)
19
+ end
20
+
21
+ private
22
+
23
+ def initialize_batches(options)
24
+ if options[:batches]
25
+ expand_given_batches_to_full_test_identifiers(options)
26
+ else
27
+ derive_batches_from_tests(options)
14
28
  end
15
- @xctestrun_path = options[:xctestrun] || derived_testrun_path(options[:derived_data_path], options[:scheme])
16
- unless @xctestrun_path && File.exist?(@xctestrun_path)
17
- FastlaneCore::UI.user_error!("Error: cannot find xctestrun file '#{@xctestrun_path}'")
29
+ end
30
+
31
+ def expand_given_batches_to_full_test_identifiers(options)
32
+ @batches = options[:batches]
33
+ testables = Set.new
34
+ @batches.each do |batch|
35
+ expand_test_identifiers(batch)
36
+ batch.each { |t| testables << t.split('/')[0] }
18
37
  end
19
- @only_testing = options[:only_testing] || only_testing_from_testplan(options)
20
- if @only_testing.kind_of?(String)
21
- @only_testing = @only_testing.split(',')
38
+ @testables = testables.to_a
39
+ end
40
+
41
+ def derive_batch_count(options)
42
+ batch_count = options.fetch(:batch_count, 1)
43
+ if batch_count == 1 && options.fetch(:parallel_testrun_count, 0) > 1
44
+ # if the batch count is 1, and the users wants parallel runs
45
+ # we *must* set the batch count to the same number of parallel
46
+ # runs or else the desired reports will not be written
47
+ batch_count = options[:parallel_testrun_count]
22
48
  end
23
- @skip_testing = options[:skip_testing]
24
- @invocation_based_tests = options[:invocation_based_tests]
25
- @batch_count = options[:batch_count]
26
- if @batch_count == 1 && options[:parallel_testrun_count] > 1
27
- @batch_count = options[:parallel_testrun_count]
49
+ batch_count
50
+ end
51
+
52
+ def derive_only_testing(options)
53
+ only_testing = options[:only_testing] || self.class.only_testing_from_testplan(options)
54
+ if only_testing && only_testing.kind_of?(String)
55
+ only_testing = only_testing.split(',').map(&:strip)
28
56
  end
57
+ only_testing
58
+ end
29
59
 
30
- @swift_test_prefix = options[:swift_test_prefix]
60
+ def testable_tests_hash_from_options(options)
61
+ testable_tests_hash = Hash.new { |h, k| h[k] = [] }
62
+ only_testing = derive_only_testing(options)
63
+ if only_testing
64
+ expand_test_identifiers(only_testing)
65
+ only_testing.each do |test_identifier|
66
+ testable = test_identifier.split('/')[0]
67
+ testable_tests_hash[testable] << test_identifier
68
+ end
69
+ else
70
+ testable_tests_hash = xctestrun_known_tests.clone
71
+ if options[:skip_testing]
72
+ expand_test_identifiers(options[:skip_testing])
73
+ testable_tests_hash.each do |testable, test_identifiers|
74
+ test_identifiers.replace(test_identifiers - options[:skip_testing])
75
+ testable_tests_hash.delete(testable) if test_identifiers.empty?
76
+ end
77
+ end
78
+ end
79
+ testable_tests_hash
80
+ end
81
+
82
+ def derive_batches_from_tests(options)
83
+ @batches = []
84
+ testable_tests_hash = testable_tests_hash_from_options(options)
85
+ @testables = testable_tests_hash.keys
86
+ batch_count = derive_batch_count(options)
87
+ testable_tests_hash.each do |testable, test_identifiers|
88
+ next if test_identifiers.empty?
89
+
90
+ if batch_count > 1
91
+ slice_count = [(test_identifiers.length / batch_count.to_f).ceil, 1].max
92
+ test_identifiers.each_slice(slice_count).to_a.each do |batch|
93
+ @batches << batch
94
+ end
95
+ else
96
+ @batches << test_identifiers
97
+ end
98
+ end
99
+ end
100
+
101
+ def expand_test_identifiers(test_identifiers)
102
+ all_known_tests = nil
103
+ test_identifiers.each_with_index do |test_identifier, index|
104
+ test_components = test_identifier.split('/')
105
+ is_full_test_identifier = (test_components.size == 3)
106
+ next if is_full_test_identifier
107
+
108
+ all_known_tests ||= xctestrun_known_tests.clone
109
+
110
+ testsuite = ''
111
+ testable = test_components[0]
112
+ expanded_test_identifiers = []
113
+ if test_components.size == 1
114
+ # this is a testable, also known as a test target. Let's expand it out
115
+ # to all of its tests. Note: a test target can have many testSuites, each
116
+ # with their own testCases.
117
+ if all_known_tests[testable].to_a.empty?
118
+ FastlaneCore::UI.verbose("Unable to expand #{testable} to constituent tests")
119
+ expanded_test_identifiers = [testable]
120
+ else
121
+ expanded_test_identifiers = all_known_tests[testable]
122
+ end
123
+ else
124
+ # this is a testable and a test suite, let's expand it out to all of
125
+ # its testCases. Note: if the user put the same test identifier in more than
126
+ # one place in this array, this could lead to multiple repititions of the same
127
+ # set of test identifiers
128
+ testsuite = test_components[1]
129
+ expanded_test_identifiers = all_known_tests[testable].select do |known_test|
130
+ known_test.split('/')[1] == testsuite
131
+ end
132
+ end
133
+ test_identifiers.delete_at(index)
134
+ test_identifiers.insert(index, *expanded_test_identifiers)
135
+ end
136
+ end
137
+
138
+ def xctestrun_known_tests
139
+ unless @known_tests
140
+ config = FastlaneCore::Configuration.create(
141
+ ::Fastlane::Actions::TestsFromXctestrunAction.available_options,
142
+ {
143
+ xctestrun: @xctestrun_path,
144
+ invocation_based_tests: @invocation_based_tests,
145
+ swift_test_prefix: @swift_test_prefix
146
+ }
147
+ )
148
+ @known_tests = ::Fastlane::Actions::TestsFromXctestrunAction.run(config)
149
+ end
150
+ @known_tests
31
151
  end
32
152
 
33
- def only_testing_from_testplan(options)
153
+ def self.only_testing_from_testplan(options)
34
154
  return unless options[:testplan] && options[:scheme]
35
155
 
36
156
  config = FastlaneCore::Configuration.create(
@@ -60,153 +180,26 @@ module TestCenter
60
180
  return test_options[:only_testing]
61
181
  end
62
182
 
63
- def default_derived_data_path(options)
183
+ def self.default_derived_data_path
64
184
  project_derived_data_path = Scan.project.build_settings(key: "BUILT_PRODUCTS_DIR")
65
185
  File.expand_path("../../..", project_derived_data_path)
66
186
  end
67
187
 
68
- def derived_testrun_path(derived_data_path, scheme)
188
+ def self.derived_testrun_path(derived_data_path)
69
189
  xctestrun_files = Dir.glob("#{derived_data_path}/Build/Products/*.xctestrun")
70
190
  xctestrun_files.sort { |f1, f2| File.mtime(f1) <=> File.mtime(f2) }.last
71
191
  end
72
192
 
73
- def testables
74
- unless @testables
75
- if @only_testing
76
- @testables ||= only_testing_to_testables_tests.keys
77
- else
78
- @testables = xctestrun_known_tests.keys
79
- end
80
- end
81
- @testables
82
- end
83
-
84
- def only_testing_to_testables_tests
85
- tests = Hash.new { |h, k| h[k] = [] }
86
- @only_testing.sort.each do |test_identifier|
87
- testable = test_identifier.split('/', 2)[0]
88
- tests[testable] << test_identifier
89
- end
90
- tests
91
- end
92
-
93
- def xctestrun_known_tests
94
- unless @known_tests
95
- config = FastlaneCore::Configuration.create(
96
- ::Fastlane::Actions::TestsFromXctestrunAction.available_options,
97
- {
98
- xctestrun: @xctestrun_path,
99
- invocation_based_tests: @invocation_based_tests,
100
- swift_test_prefix: @swift_test_prefix
101
- }
102
- )
103
- @known_tests = ::Fastlane::Actions::TestsFromXctestrunAction.run(config)
104
- end
105
- @known_tests
106
- end
107
-
108
- def expand_short_testidentifiers_to_tests(testables_tests)
109
- # Remember, testable_tests is of the format:
110
- # {
111
- # 'testable1' => [
112
- # 'testsuite1/testcase1',
113
- # 'testsuite1/testcase2',
114
- # 'testsuite2/testcase1',
115
- # 'testsuite2/testcase2',
116
- # ...
117
- # 'testsuiteN/testcase1', ... 'testsuiteN/testcaseM'
118
- # ],
119
- # ...
120
- # 'testableO' => [
121
- # 'testsuite1/testcase1',
122
- # 'testsuite1/testcase2',
123
- # 'testsuite2/testcase1',
124
- # 'testsuite2/testcase2',
125
- # ...
126
- # 'testsuiteN/testcase1', ... 'testsuiteN/testcaseM'
127
- # ]
128
- # }
129
- return if @invocation_based_tests
130
-
131
- # iterate among all the test identifers for each testable
132
- # A test identifier is seperated into components by '/'
133
- # if a test identifier has only 2 separators, it probably is
134
- # 'testable/testsuite' (but it could be 'testsuite/testcase' )
135
- all_known_tests = nil
136
- known_tests = []
137
- testables_tests.each do |testable, tests|
138
- tests.each_with_index do |test, index|
139
- test_components = test.split('/')
140
- is_full_test_identifier = (test_components.size == 3)
141
- next if is_full_test_identifier
142
-
143
- all_known_tests ||= xctestrun_known_tests.clone
144
-
145
- testsuite = ''
146
- if test_components.size == 1
147
- if test_components[0] == testable
148
- # The following || [] is just in case the user provided multiple
149
- # test targets or there are no actual tests found.
150
- testables_tests[testable][index] = all_known_tests[testable] || []
151
- all_known_tests.delete(testable)
152
- next
153
- end
154
-
155
- testsuite = test_components[0]
156
- else
157
- testsuite = test_components[1]
158
- end
159
-
160
- testables_tests[testable][index], all_known_tests[testable] = all_known_tests[testable].partition do |known_test|
161
- known_test.split('/')[1] == testsuite
162
- end
163
- end
164
- testables_tests[testable].flatten!
165
- end
166
- end
167
-
168
- def testables_tests
169
- unless @testables_tests
170
- if @only_testing
171
- @testables_tests = only_testing_to_testables_tests
172
- expand_short_testidentifiers_to_tests(@testables_tests)
173
- else
174
- @testables_tests = xctestrun_known_tests
175
- if @skip_testing
176
- skipped_testable_tests = Hash.new { |h, k| h[k] = [] }
177
- @skip_testing.sort.each do |skipped_test_identifier|
178
- testable = skipped_test_identifier.split('/', 2)[0]
179
- skipped_testable_tests[testable] << skipped_test_identifier
180
- end
181
- @testables_tests.each_key do |testable|
182
- @testables_tests[testable] -= skipped_testable_tests[testable]
183
- end
184
- end
185
- end
193
+ def self.xctestrun_filepath(options)
194
+ unless options[:xctestrun] || options[:derived_data_path]
195
+ options[:derived_data_path] = default_derived_data_path
186
196
  end
197
+ path = (options[:xctestrun] || derived_testrun_path(options[:derived_data_path]))
187
198
 
188
- @testables_tests
189
- end
190
-
191
- def test_batches
192
- if @batches.nil?
193
- @batches = []
194
- testables.each do |testable|
195
- testable_tests = testables_tests[testable]
196
- next if testable_tests.empty?
197
-
198
- if @batch_count > 1
199
- slice_count = [(testable_tests.length / @batch_count.to_f).ceil, 1].max
200
- testable_tests.each_slice(slice_count).to_a.each do |tests_batch|
201
- @batches << tests_batch
202
- end
203
- else
204
- @batches << testable_tests
205
- end
206
- end
199
+ unless path && File.exist?(path)
200
+ FastlaneCore::UI.user_error!("Error: cannot find xctestrun file '#{path}'")
207
201
  end
208
-
209
- @batches
202
+ path
210
203
  end
211
204
  end
212
205
  end
@@ -1,5 +1,5 @@
1
1
  module Fastlane
2
2
  module TestCenter
3
- VERSION = "3.14.1"
3
+ VERSION = "3.14.6"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fastlane-plugin-test_center
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.14.1
4
+ version: 3.14.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lyndsey Ferguson
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-17 00:00:00.000000000 Z
11
+ date: 2020-10-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: trainer
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: xcodeproj
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -270,6 +284,7 @@ files:
270
284
  - lib/fastlane/plugin/test_center/actions/test_options_from_testplan.rb
271
285
  - lib/fastlane/plugin/test_center/actions/testplans_from_scheme.rb
272
286
  - lib/fastlane/plugin/test_center/actions/tests_from_junit.rb
287
+ - lib/fastlane/plugin/test_center/actions/tests_from_xcresult.rb
273
288
  - lib/fastlane/plugin/test_center/actions/tests_from_xctestrun.rb
274
289
  - lib/fastlane/plugin/test_center/helper/fastlane_core/device_manager/simulator_extensions.rb
275
290
  - lib/fastlane/plugin/test_center/helper/html_test_report.rb
@@ -295,7 +310,7 @@ homepage: https://github.com/lyndsey-ferguson/fastlane-plugin-test_center
295
310
  licenses:
296
311
  - MIT
297
312
  metadata: {}
298
- post_install_message:
313
+ post_install_message:
299
314
  rdoc_options: []
300
315
  require_paths:
301
316
  - lib
@@ -310,8 +325,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
310
325
  - !ruby/object:Gem::Version
311
326
  version: '0'
312
327
  requirements: []
313
- rubygems_version: 3.0.3
314
- signing_key:
328
+ rubygems_version: 3.1.2
329
+ signing_key:
315
330
  specification_version: 4
316
331
  summary: Makes testing your iOS app easier
317
332
  test_files: []