fastlane-plugin-stream_actions 0.4.15 → 0.4.17
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/lib/fastlane/plugin/stream_actions/actions/build_test_coverage_map.rb +37 -4
- data/lib/fastlane/plugin/stream_actions/actions/publish_ios_sdk.rb +25 -2
- data/lib/fastlane/plugin/stream_actions/actions/select_impacted_tests.rb +4 -3
- data/lib/fastlane/plugin/stream_actions/helper/test_coverage_map_helper.rb +20 -0
- data/lib/fastlane/plugin/stream_actions/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 46352486158d9628e7e172f2df0b4ab29f6b0ac575e66fd8fe8c92a014c43b42
|
|
4
|
+
data.tar.gz: fad45a69555125e8bc8b55e026e47b1fb35348450abdcc0357c176c57a4ecd21
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9a5a36c1870b78a0764e92eaf5a6374b189df978c29b116815622cd123a5c48588408170265724c9601c2e607589ed5a1c93e8fcc012152d6ced161363511fa7
|
|
7
|
+
data.tar.gz: 1f15575af4166ba23aa48d292bd0e1590a58b1094c8a3678235531af6727b8a384264f18f194c534555b0b47d91a72a8250f1c132dc762d012ad99fc0ac64f12
|
|
@@ -9,7 +9,14 @@ module Fastlane
|
|
|
9
9
|
class BuildTestCoverageMapAction < Action
|
|
10
10
|
def self.run(params)
|
|
11
11
|
source_root = File.expand_path(params[:source_root] || Dir.pwd)
|
|
12
|
-
sources, tests =
|
|
12
|
+
sources, tests =
|
|
13
|
+
if params[:class_dirs]
|
|
14
|
+
jacoco_coverage(params, source_root)
|
|
15
|
+
elsif params[:binaries]
|
|
16
|
+
llvm_coverage(params, source_root)
|
|
17
|
+
else
|
|
18
|
+
lcov_coverage(params, source_root)
|
|
19
|
+
end
|
|
13
20
|
|
|
14
21
|
carry_over_failed_tests(tests, sources, params) unless params[:failed_tests].to_a.empty?
|
|
15
22
|
drop_untracked(tests, sources, source_root)
|
|
@@ -31,7 +38,6 @@ module Fastlane
|
|
|
31
38
|
test_dirs = Dir.glob(File.join(params[:coverage_dir], '*', '*')).select { |dir| File.directory?(dir) }
|
|
32
39
|
UI.user_error!("No per-test coverage found in '#{params[:coverage_dir]}'") if test_dirs.empty?
|
|
33
40
|
|
|
34
|
-
UI.user_error!('Pass the instrumented binaries or the class_dirs') if params[:binaries].to_a.empty?
|
|
35
41
|
binaries = params[:binaries].select { |binary| File.exist?(binary) }
|
|
36
42
|
UI.user_error!('None of the instrumented binaries exist') if binaries.empty?
|
|
37
43
|
|
|
@@ -88,6 +94,24 @@ module Fastlane
|
|
|
88
94
|
[source_of_class.values.flatten.uniq, tests]
|
|
89
95
|
end
|
|
90
96
|
|
|
97
|
+
# Flutter: one folder of lcov files per test, `<coverage_dir>/<TestFile>/<test description>/`, e.g. from
|
|
98
|
+
# `flutter test --coverage --coverage-path <dir>/lcov.info --name <test>`. lcov lists every library the app loaded,
|
|
99
|
+
# so a file counts as executed when one of its lines has hits.
|
|
100
|
+
def self.lcov_coverage(params, source_root)
|
|
101
|
+
reports = Helper::TestCoverageMapHelper.lcov_reports(params[:coverage_dir])
|
|
102
|
+
UI.user_error!("No per-test coverage found in '#{params[:coverage_dir]}'") if reports.empty?
|
|
103
|
+
|
|
104
|
+
UI.message("Building the coverage map of #{reports.size} tests ⌛️")
|
|
105
|
+
base_dir = File.expand_path(params[:lcov_base_dir] || source_root)
|
|
106
|
+
sources = []
|
|
107
|
+
tests = reports.transform_values do |files|
|
|
108
|
+
files = files.map { |file, executed| [relative_path(File.expand_path(file, base_dir), source_root), executed] }.reject { |file, _| file.nil? }
|
|
109
|
+
sources |= files.map(&:first)
|
|
110
|
+
files.select(&:last).map(&:first).uniq
|
|
111
|
+
end
|
|
112
|
+
[sources, tests]
|
|
113
|
+
end
|
|
114
|
+
|
|
91
115
|
# Class name => `<package path>/<source file name>`, from an XML report of the class files without execution data.
|
|
92
116
|
# Classes without code (interfaces, annotations, constants) are empty `<class/>` elements and stay out, like
|
|
93
117
|
# declaration-only files in llvm-cov. One report per folder, as JaCoCo fails on a class name that two folders
|
|
@@ -234,7 +258,7 @@ module Fastlane
|
|
|
234
258
|
#####################################################
|
|
235
259
|
|
|
236
260
|
def self.description
|
|
237
|
-
'Builds a map of which source files every test executes, from per-test LLVM or
|
|
261
|
+
'Builds a map of which source files every test executes, from per-test LLVM, JaCoCo or lcov coverage'
|
|
238
262
|
end
|
|
239
263
|
|
|
240
264
|
def self.details
|
|
@@ -248,6 +272,9 @@ module Fastlane
|
|
|
248
272
|
`<package>.<TestClass>#<testMethod>.ec`, which the test orchestrator writes with `-e coverage true
|
|
249
273
|
-e coverageFilePath <dir>/`. A source file counts as executed when one of its classes ran a probe.
|
|
250
274
|
|
|
275
|
+
Flutter: pass neither, and a `coverage_dir` with one folder of lcov `.info` files per test, `<TestFile>/<test>/`,
|
|
276
|
+
e.g. from `flutter test --coverage --name <test>`. A source file counts as executed when one of its lines has hits.
|
|
277
|
+
|
|
251
278
|
The resulting JSON map feeds `select_impacted_tests`.
|
|
252
279
|
DETAILS
|
|
253
280
|
end
|
|
@@ -260,7 +287,8 @@ module Fastlane
|
|
|
260
287
|
[
|
|
261
288
|
FastlaneCore::ConfigItem.new(
|
|
262
289
|
key: :coverage_dir,
|
|
263
|
-
description: 'Folder with per-test
|
|
290
|
+
description: 'Folder with per-test coverage: <TestClass>/<testMethod>/*.profraw (iOS), *.ec files (Android) ' \
|
|
291
|
+
'or <TestFile>/<test>/*.info (Flutter)',
|
|
264
292
|
verify_block: proc { |path| UI.user_error!("Coverage folder '#{path}' does not exist") unless File.directory?(path) }
|
|
265
293
|
),
|
|
266
294
|
FastlaneCore::ConfigItem.new(
|
|
@@ -280,6 +308,11 @@ module Fastlane
|
|
|
280
308
|
description: 'Android: path to the JaCoCo CLI jar (org.jacoco.cli nodeps). Downloaded when not set',
|
|
281
309
|
optional: true
|
|
282
310
|
),
|
|
311
|
+
FastlaneCore::ConfigItem.new(
|
|
312
|
+
key: :lcov_base_dir,
|
|
313
|
+
description: 'Flutter: folder that relative lcov SF paths start from (the package that ran the tests), source_root by default',
|
|
314
|
+
optional: true
|
|
315
|
+
),
|
|
283
316
|
FastlaneCore::ConfigItem.new(
|
|
284
317
|
key: :source_root,
|
|
285
318
|
description: 'Repository root, the current directory by default (fastlane runs actions from the repository root). ' \
|
|
@@ -17,7 +17,8 @@ module Fastlane
|
|
|
17
17
|
version_number
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
make_latest = params[:make_latest]
|
|
21
|
+
release = other_action.set_github_release(
|
|
21
22
|
repository_name: params[:github_repo],
|
|
22
23
|
api_token: params[:github_token],
|
|
23
24
|
name: version_number,
|
|
@@ -25,8 +26,10 @@ module Fastlane
|
|
|
25
26
|
description: changes,
|
|
26
27
|
commitish: other_action.current_branch,
|
|
27
28
|
upload_assets: params[:upload_assets],
|
|
28
|
-
is_prerelease: version_number.downcase.include?('beta')
|
|
29
|
+
is_prerelease: version_number.downcase.include?('beta'),
|
|
30
|
+
is_draft: !make_latest
|
|
29
31
|
)
|
|
32
|
+
publish_without_marking_latest(params, release) unless make_latest
|
|
30
33
|
end
|
|
31
34
|
|
|
32
35
|
unless skip_pods
|
|
@@ -43,6 +46,19 @@ module Fastlane
|
|
|
43
46
|
other_action.ensure_git_status_clean unless params[:skip_git_status_check]
|
|
44
47
|
end
|
|
45
48
|
|
|
49
|
+
# GitHub marks a release as Latest when it is created, so a release that must not be Latest
|
|
50
|
+
# (e.g. a patch of an older major version) is created as a draft and published with `make_latest: false`.
|
|
51
|
+
def self.publish_without_marking_latest(params, release)
|
|
52
|
+
UI.user_error!('Failed to create the GitHub release') unless release.kind_of?(Hash) && release['id']
|
|
53
|
+
|
|
54
|
+
other_action.github_api(
|
|
55
|
+
api_token: params[:github_token],
|
|
56
|
+
http_method: 'PATCH',
|
|
57
|
+
path: "repos/#{params[:github_repo]}/releases/#{release['id']}",
|
|
58
|
+
body: { draft: false, make_latest: 'false' }
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
46
62
|
def self.ensure_release_tag_is_new(version_number)
|
|
47
63
|
if other_action.git_tag_exists(tag: version_number)
|
|
48
64
|
UI.user_error!("Tag for version #{version_number} already exists!")
|
|
@@ -132,6 +148,13 @@ module Fastlane
|
|
|
132
148
|
is_string: true,
|
|
133
149
|
optional: true
|
|
134
150
|
),
|
|
151
|
+
FastlaneCore::ConfigItem.new(
|
|
152
|
+
key: :make_latest,
|
|
153
|
+
description: 'Mark the GitHub release as Latest? Pass false for releases of an older major version',
|
|
154
|
+
is_string: false,
|
|
155
|
+
optional: true,
|
|
156
|
+
default_value: true
|
|
157
|
+
),
|
|
135
158
|
FastlaneCore::ConfigItem.new(
|
|
136
159
|
key: :upload_assets,
|
|
137
160
|
description: 'Path to assets to be uploaded with the release',
|
|
@@ -6,9 +6,10 @@ module Fastlane
|
|
|
6
6
|
module Actions
|
|
7
7
|
class SelectImpactedTestsAction < Action
|
|
8
8
|
CODE_EXTENSIONS = %w[.swift .m .mm .h .kt .java .dart .js .jsx .ts .tsx].freeze
|
|
9
|
-
# Kotlin's `enum class` and `fun interface` name the type after the second keyword.
|
|
10
|
-
MODIFIERS = '(?:(?:public|open|internal|fileprivate|private|protected|final|indirect|nonisolated|data|sealed|abstract|annotation|value|inline|inner|enum(?=\\s+class\\b)|fun(?=\\s+interface\\b)|export|default)\\s+)*'
|
|
11
|
-
|
|
9
|
+
# Kotlin's `enum class` and `fun interface`, and Dart's `interface class` and `mixin class`, name the type after the second keyword.
|
|
10
|
+
MODIFIERS = '(?:(?:public|open|internal|fileprivate|private|protected|final|indirect|nonisolated|data|sealed|abstract|base|annotation|value|inline|inner|enum(?=\\s+class\\b)|fun(?=\\s+interface\\b)|interface(?=\\s+class\\b)|mixin(?=\\s+class\\b)|export|default)\\s+)*'
|
|
11
|
+
# Dart's `extension type` declares a new type, unlike an `extension`.
|
|
12
|
+
DECLARATION = /^\s*(?:@\w+(?:\([^)\n]*\))?\s+)*#{MODIFIERS}(class|struct|enum|protocol|actor|typealias|typedef|mixin|extension\s+type(?:\s+const)?|extension|interface|object)\s+([A-Za-z_]\w*)/.freeze
|
|
12
13
|
|
|
13
14
|
def self.run(params)
|
|
14
15
|
source_root = File.expand_path(params[:source_root] || Dir.pwd)
|
|
@@ -58,6 +58,26 @@ module Fastlane
|
|
|
58
58
|
end
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
+
# `TestFile/test` => [[source file, executed?], ...] of the lcov reports in `<coverage_dir>/<TestFile>/<test>/`.
|
|
62
|
+
def self.lcov_reports(coverage_dir)
|
|
63
|
+
Dir.glob(File.join(coverage_dir, '*', '*', '*.info'))
|
|
64
|
+
.group_by { |report| File.dirname(report).split('/').last(2).join('/') }
|
|
65
|
+
.transform_values { |reports| reports.flat_map { |report| lcov_files(report) } }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# [[source file, executed?], ...] of an lcov report, in the order it lists them.
|
|
69
|
+
def self.lcov_files(report)
|
|
70
|
+
files = []
|
|
71
|
+
File.foreach(report) do |line|
|
|
72
|
+
if line.start_with?('SF:')
|
|
73
|
+
files << [line[3..-1].strip, false]
|
|
74
|
+
elsif line.start_with?('DA:') && !files.empty? && line.split(',')[1].to_i.positive?
|
|
75
|
+
files.last[1] = true
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
files
|
|
79
|
+
end
|
|
80
|
+
|
|
61
81
|
def self.read_map(path)
|
|
62
82
|
map = JSON.parse(File.read(path))
|
|
63
83
|
UI.user_error!("Unsupported coverage map version #{map['version']}") unless map['version'] == MAP_VERSION
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fastlane-plugin-stream_actions
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.17
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- GetStream
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-22 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: xctest_list
|