fastlane-plugin-test_center 3.14.1 → 3.14.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/fastlane/plugin/test_center/actions/collate_html_reports.rb +2 -1
- data/lib/fastlane/plugin/test_center/actions/collate_junit_reports.rb +17 -4
- data/lib/fastlane/plugin/test_center/actions/collate_test_result_bundles.rb +6 -4
- data/lib/fastlane/plugin/test_center/actions/collate_xcresults.rb +9 -5
- data/lib/fastlane/plugin/test_center/actions/multi_scan.rb +235 -10
- data/lib/fastlane/plugin/test_center/actions/tests_from_xcresult.rb +103 -0
- data/lib/fastlane/plugin/test_center/helper/fastlane_core/device_manager/simulator_extensions.rb +6 -1
- data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan_helper.rb +23 -1
- data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/runner.rb +71 -9
- data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/test_batch_worker_pool.rb +10 -1
- data/lib/fastlane/plugin/test_center/helper/test_collector.rb +145 -152
- data/lib/fastlane/plugin/test_center/version.rb +1 -1
- metadata +21 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8d4ae676597a3e9b639c6f31c26e8b1237c821b5923a404a106637bea39c998c
|
4
|
+
data.tar.gz: 916b0ec2290cd0f572f48938c1c0860dac107fdee6be21a94d1325ec445f0825
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|

|
102
|
-
[vdavydovHH](https://github.com/vdavydovHH)
|
102
|
+
[vdavydovHH](https://github.com/vdavydovHH) [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[^"]*">)(.*(
|
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;)/, '&')
|
50
51
|
test_details.gsub!('<', '<')
|
51
52
|
test_details.gsub!('>', '>')
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
225
|
-
|
226
|
-
|
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
|
94
|
-
|
95
|
-
|
96
|
-
|
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
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
37
|
-
|
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
|
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: [
|
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
|
data/lib/fastlane/plugin/test_center/helper/fastlane_core/device_manager/simulator_extensions.rb
CHANGED
@@ -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
|
-
|
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
|
-
@
|
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.
|
69
|
-
tests = @test_collector.
|
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
|
-
|
93
|
+
test_results = [false]
|
93
94
|
if should_run_tests_through_single_try?
|
94
|
-
|
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
|
102
|
+
unless test_results.all? || @options[:try_count] < 1
|
103
|
+
test_results.clear
|
98
104
|
setup_testcollector
|
99
|
-
|
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
|
-
|
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.
|
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.
|
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[:
|
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 :
|
10
|
+
attr_reader :batches
|
11
|
+
attr_reader :testables
|
10
12
|
|
11
13
|
def initialize(options)
|
12
|
-
|
13
|
-
|
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
|
-
|
16
|
-
|
17
|
-
|
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
|
-
@
|
20
|
-
|
21
|
-
|
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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
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
|
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
|
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
|
74
|
-
unless
|
75
|
-
|
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
|
-
|
189
|
-
|
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
|
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.
|
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-
|
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.
|
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: []
|