codecov-cli 11.0.0__py3-none-any.whl

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.
Files changed (83) hide show
  1. codecov_cli/__init__.py +3 -0
  2. codecov_cli/commands/__init__.py +0 -0
  3. codecov_cli/commands/base_picking.py +75 -0
  4. codecov_cli/commands/commit.py +72 -0
  5. codecov_cli/commands/create_report_result.py +41 -0
  6. codecov_cli/commands/empty_upload.py +80 -0
  7. codecov_cli/commands/get_report_results.py +50 -0
  8. codecov_cli/commands/labelanalysis.py +269 -0
  9. codecov_cli/commands/process_test_results.py +273 -0
  10. codecov_cli/commands/report.py +65 -0
  11. codecov_cli/commands/send_notifications.py +46 -0
  12. codecov_cli/commands/staticanalysis.py +62 -0
  13. codecov_cli/commands/upload.py +316 -0
  14. codecov_cli/commands/upload_coverage.py +186 -0
  15. codecov_cli/commands/upload_process.py +133 -0
  16. codecov_cli/fallbacks.py +41 -0
  17. codecov_cli/helpers/__init__.py +0 -0
  18. codecov_cli/helpers/args.py +31 -0
  19. codecov_cli/helpers/ci_adapters/__init__.py +63 -0
  20. codecov_cli/helpers/ci_adapters/appveyor_ci.py +54 -0
  21. codecov_cli/helpers/ci_adapters/azure_pipelines.py +44 -0
  22. codecov_cli/helpers/ci_adapters/base.py +102 -0
  23. codecov_cli/helpers/ci_adapters/bitbucket_ci.py +42 -0
  24. codecov_cli/helpers/ci_adapters/bitrise_ci.py +37 -0
  25. codecov_cli/helpers/ci_adapters/buildkite.py +45 -0
  26. codecov_cli/helpers/ci_adapters/circleci.py +47 -0
  27. codecov_cli/helpers/ci_adapters/cirrus_ci.py +36 -0
  28. codecov_cli/helpers/ci_adapters/cloudbuild.py +70 -0
  29. codecov_cli/helpers/ci_adapters/codebuild.py +49 -0
  30. codecov_cli/helpers/ci_adapters/droneci.py +36 -0
  31. codecov_cli/helpers/ci_adapters/github_actions.py +90 -0
  32. codecov_cli/helpers/ci_adapters/gitlab_ci.py +56 -0
  33. codecov_cli/helpers/ci_adapters/heroku.py +36 -0
  34. codecov_cli/helpers/ci_adapters/jenkins.py +38 -0
  35. codecov_cli/helpers/ci_adapters/local.py +39 -0
  36. codecov_cli/helpers/ci_adapters/teamcity.py +37 -0
  37. codecov_cli/helpers/ci_adapters/travis_ci.py +44 -0
  38. codecov_cli/helpers/ci_adapters/woodpeckerci.py +36 -0
  39. codecov_cli/helpers/config.py +66 -0
  40. codecov_cli/helpers/encoder.py +49 -0
  41. codecov_cli/helpers/folder_searcher.py +114 -0
  42. codecov_cli/helpers/git.py +97 -0
  43. codecov_cli/helpers/git_services/__init__.py +14 -0
  44. codecov_cli/helpers/git_services/github.py +40 -0
  45. codecov_cli/helpers/glob.py +146 -0
  46. codecov_cli/helpers/logging_utils.py +77 -0
  47. codecov_cli/helpers/options.py +51 -0
  48. codecov_cli/helpers/request.py +198 -0
  49. codecov_cli/helpers/upload_type.py +15 -0
  50. codecov_cli/helpers/validators.py +13 -0
  51. codecov_cli/helpers/versioning_systems.py +201 -0
  52. codecov_cli/main.py +99 -0
  53. codecov_cli/opentelemetry.py +26 -0
  54. codecov_cli/plugins/__init__.py +92 -0
  55. codecov_cli/plugins/compress_pycoverage_contexts.py +141 -0
  56. codecov_cli/plugins/gcov.py +69 -0
  57. codecov_cli/plugins/pycoverage.py +134 -0
  58. codecov_cli/plugins/types.py +8 -0
  59. codecov_cli/plugins/xcode.py +117 -0
  60. codecov_cli/runners/__init__.py +80 -0
  61. codecov_cli/runners/dan_runner.py +64 -0
  62. codecov_cli/runners/pytest_standard_runner.py +184 -0
  63. codecov_cli/runners/types.py +33 -0
  64. codecov_cli/services/__init__.py +0 -0
  65. codecov_cli/services/commit/__init__.py +86 -0
  66. codecov_cli/services/commit/base_picking.py +24 -0
  67. codecov_cli/services/empty_upload/__init__.py +42 -0
  68. codecov_cli/services/report/__init__.py +169 -0
  69. codecov_cli/services/upload/__init__.py +169 -0
  70. codecov_cli/services/upload/file_finder.py +320 -0
  71. codecov_cli/services/upload/legacy_upload_sender.py +132 -0
  72. codecov_cli/services/upload/network_finder.py +49 -0
  73. codecov_cli/services/upload/upload_collector.py +198 -0
  74. codecov_cli/services/upload/upload_sender.py +232 -0
  75. codecov_cli/services/upload_completion/__init__.py +38 -0
  76. codecov_cli/services/upload_coverage/__init__.py +93 -0
  77. codecov_cli/types.py +88 -0
  78. codecov_cli-11.0.0.dist-info/METADATA +298 -0
  79. codecov_cli-11.0.0.dist-info/RECORD +83 -0
  80. codecov_cli-11.0.0.dist-info/WHEEL +5 -0
  81. codecov_cli-11.0.0.dist-info/entry_points.txt +3 -0
  82. codecov_cli-11.0.0.dist-info/licenses/LICENSE +201 -0
  83. codecov_cli-11.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,3 @@
1
+ import importlib.metadata
2
+
3
+ __version__ = importlib.metadata.version("codecov-cli")
File without changes
@@ -0,0 +1,75 @@
1
+ import logging
2
+ import typing
3
+
4
+ import click
5
+ import sentry_sdk
6
+
7
+ from codecov_cli.fallbacks import CodecovOption, FallbackFieldEnum
8
+ from codecov_cli.helpers.args import get_cli_args
9
+ from codecov_cli.helpers.encoder import slug_without_subgroups_is_invalid
10
+ from codecov_cli.services.commit.base_picking import base_picking_logic
11
+ from codecov_cli.types import CommandContext
12
+
13
+ logger = logging.getLogger("codecovcli")
14
+
15
+
16
+ @click.command()
17
+ @click.option(
18
+ "--base-sha",
19
+ help="Base commit SHA (with 40 chars)",
20
+ cls=CodecovOption,
21
+ fallback_field=FallbackFieldEnum.commit_sha,
22
+ required=True,
23
+ )
24
+ @click.option(
25
+ "--pr",
26
+ help="Pull Request id to associate commit with",
27
+ cls=CodecovOption,
28
+ fallback_field=FallbackFieldEnum.pull_request_number,
29
+ )
30
+ @click.option(
31
+ "--slug",
32
+ cls=CodecovOption,
33
+ fallback_field=FallbackFieldEnum.slug,
34
+ help="owner/repo slug",
35
+ envvar="CODECOV_SLUG",
36
+ )
37
+ @click.option(
38
+ "-t",
39
+ "--token",
40
+ help="Codecov upload token",
41
+ envvar="CODECOV_TOKEN",
42
+ )
43
+ @click.option(
44
+ "--service",
45
+ cls=CodecovOption,
46
+ fallback_field=FallbackFieldEnum.service,
47
+ help="Specify the service provider of the repo e.g. github",
48
+ )
49
+ @click.pass_context
50
+ def pr_base_picking(
51
+ ctx: CommandContext,
52
+ base_sha: str,
53
+ pr: typing.Optional[int],
54
+ slug: typing.Optional[str],
55
+ token: typing.Optional[str],
56
+ service: typing.Optional[str],
57
+ ):
58
+ with sentry_sdk.start_transaction(op="task", name="Base Picking"):
59
+ with sentry_sdk.start_span(name="base_picking"):
60
+ enterprise_url = ctx.obj.get("enterprise_url")
61
+ args = get_cli_args(ctx)
62
+ logger.debug(
63
+ "Starting base picking process",
64
+ extra=dict(
65
+ extra_log_attributes=args,
66
+ ),
67
+ )
68
+
69
+ if slug_without_subgroups_is_invalid(slug):
70
+ logger.error(
71
+ "Slug is invalid. Slug should be in the form of owner_username/repo_name"
72
+ )
73
+ return
74
+
75
+ base_picking_logic(base_sha, pr, slug, token, service, enterprise_url, args)
@@ -0,0 +1,72 @@
1
+ import logging
2
+ import typing
3
+
4
+ import click
5
+ import sentry_sdk
6
+
7
+ from codecov_cli.fallbacks import CodecovOption, FallbackFieldEnum
8
+ from codecov_cli.helpers.args import get_cli_args
9
+ from codecov_cli.helpers.git import GitService
10
+ from codecov_cli.helpers.options import global_options
11
+ from codecov_cli.services.commit import create_commit_logic
12
+ from codecov_cli.types import CommandContext
13
+
14
+ logger = logging.getLogger("codecovcli")
15
+
16
+
17
+ @click.command()
18
+ @click.option(
19
+ "--parent-sha",
20
+ help="SHA (with 40 chars) of what should be the parent of this commit",
21
+ )
22
+ @click.option(
23
+ "-P",
24
+ "--pr",
25
+ "--pull-request-number",
26
+ "pull_request_number",
27
+ help="Specify the pull request number manually. Used to override pre-existing CI environment variables",
28
+ cls=CodecovOption,
29
+ fallback_field=FallbackFieldEnum.pull_request_number,
30
+ )
31
+ @click.option(
32
+ "-B",
33
+ "--branch",
34
+ help="Branch to which this commit belongs to",
35
+ cls=CodecovOption,
36
+ fallback_field=FallbackFieldEnum.branch,
37
+ )
38
+ @global_options
39
+ @click.pass_context
40
+ def create_commit(
41
+ ctx: CommandContext,
42
+ commit_sha: str,
43
+ parent_sha: typing.Optional[str],
44
+ pull_request_number: typing.Optional[int],
45
+ branch: typing.Optional[str],
46
+ slug: typing.Optional[str],
47
+ token: typing.Optional[str],
48
+ git_service: typing.Optional[str],
49
+ fail_on_error: bool,
50
+ ):
51
+ with sentry_sdk.start_transaction(op="task", name="Create Commit"):
52
+ with sentry_sdk.start_span(name="create_commit"):
53
+ enterprise_url = ctx.obj.get("enterprise_url")
54
+ args = get_cli_args(ctx)
55
+ logger.debug(
56
+ "Starting create commit process",
57
+ extra=dict(
58
+ extra_log_attributes=args,
59
+ ),
60
+ )
61
+ create_commit_logic(
62
+ commit_sha,
63
+ parent_sha,
64
+ pull_request_number,
65
+ branch,
66
+ slug,
67
+ token,
68
+ git_service,
69
+ enterprise_url,
70
+ fail_on_error,
71
+ args,
72
+ )
@@ -0,0 +1,41 @@
1
+ import logging
2
+
3
+ import click
4
+ import sentry_sdk
5
+
6
+ from codecov_cli.helpers.args import get_cli_args
7
+ from codecov_cli.helpers.options import global_options
8
+ from codecov_cli.services.report import create_report_results_logic
9
+ from codecov_cli.types import CommandContext
10
+
11
+ logger = logging.getLogger("codecovcli")
12
+
13
+
14
+ @click.command(hidden=True, deprecated=True)
15
+ @click.option(
16
+ "--code", help="The code of the report. If unsure, leave default", default="default"
17
+ )
18
+ @global_options
19
+ @click.pass_context
20
+ def create_report_results(
21
+ ctx: CommandContext,
22
+ commit_sha: str,
23
+ code: str,
24
+ slug: str,
25
+ git_service: str,
26
+ token: str,
27
+ fail_on_error: bool,
28
+ ):
29
+ with sentry_sdk.start_transaction(op="task", name="Create Report Result"):
30
+ with sentry_sdk.start_span(name="create_report_result"):
31
+ enterprise_url = ctx.obj.get("enterprise_url")
32
+ args = get_cli_args(ctx)
33
+ logger.debug(
34
+ "Creating report results",
35
+ extra=dict(
36
+ extra_log_attributes=args,
37
+ ),
38
+ )
39
+ create_report_results_logic(
40
+ commit_sha, code, slug, git_service, token, enterprise_url, fail_on_error, args
41
+ )
@@ -0,0 +1,80 @@
1
+ import logging
2
+ import typing
3
+
4
+ import click
5
+ import sentry_sdk
6
+
7
+ from codecov_cli.fallbacks import CodecovOption, FallbackFieldEnum
8
+ from codecov_cli.helpers.args import get_cli_args
9
+ from codecov_cli.helpers.options import global_options
10
+ from codecov_cli.services.commit import create_commit_logic
11
+ from codecov_cli.services.empty_upload import empty_upload_logic
12
+ from codecov_cli.types import CommandContext
13
+
14
+ logger = logging.getLogger("codecovcli")
15
+
16
+
17
+ @click.command()
18
+ @click.option("--force", is_flag=True, default=False)
19
+ @click.option(
20
+ "--parent-sha",
21
+ help="SHA (with 40 chars) of what should be the parent of this commit",
22
+ )
23
+ @click.option(
24
+ "-P",
25
+ "--pr",
26
+ "--pull-request-number",
27
+ "pull_request_number",
28
+ help="Specify the pull request number manually. Used to override pre-existing CI environment variables",
29
+ cls=CodecovOption,
30
+ fallback_field=FallbackFieldEnum.pull_request_number,
31
+ )
32
+ @click.option(
33
+ "-B",
34
+ "--branch",
35
+ help="Branch to which this commit belongs to",
36
+ cls=CodecovOption,
37
+ fallback_field=FallbackFieldEnum.branch,
38
+ )
39
+ @global_options
40
+ @click.pass_context
41
+ def empty_upload(
42
+ ctx: CommandContext,
43
+ commit_sha: str,
44
+ force: bool,
45
+ slug: typing.Optional[str],
46
+ token: typing.Optional[str],
47
+ git_service: typing.Optional[str],
48
+ fail_on_error: typing.Optional[bool],
49
+ parent_sha: typing.Optional[str],
50
+ pull_request_number: typing.Optional[int],
51
+ branch: typing.Optional[str],
52
+ ):
53
+ with sentry_sdk.start_transaction(op="task", name="Empty Upload"):
54
+ with sentry_sdk.start_span(name="empty_upload"):
55
+ enterprise_url = ctx.obj.get("enterprise_url")
56
+ args = get_cli_args(ctx)
57
+
58
+ logger.debug("Attempting to Create Commit before doing an empty upload.")
59
+ create_commit_logic(
60
+ commit_sha,
61
+ parent_sha,
62
+ pull_request_number,
63
+ branch,
64
+ slug,
65
+ token,
66
+ git_service,
67
+ enterprise_url,
68
+ fail_on_error,
69
+ args,
70
+ )
71
+
72
+ logger.debug(
73
+ "Starting empty upload process",
74
+ extra=dict(
75
+ extra_log_attributes=args,
76
+ ),
77
+ )
78
+ return empty_upload_logic(
79
+ commit_sha, slug, token, git_service, enterprise_url, fail_on_error, force, args
80
+ )
@@ -0,0 +1,50 @@
1
+ import logging
2
+
3
+ import click
4
+ import sentry_sdk
5
+
6
+ from codecov_cli.helpers.args import get_cli_args
7
+ from codecov_cli.helpers.encoder import encode_slug
8
+ from codecov_cli.helpers.options import global_options
9
+ from codecov_cli.services.report import send_reports_result_get_request
10
+ from codecov_cli.types import CommandContext
11
+
12
+
13
+ logger = logging.getLogger("codecovcli")
14
+
15
+
16
+ @click.command(hidden=True, deprecated=True)
17
+ @click.option(
18
+ "--code", help="The code of the report. If unsure, leave default", default="default"
19
+ )
20
+ @global_options
21
+ @click.pass_context
22
+ def get_report_results(
23
+ ctx: CommandContext,
24
+ commit_sha: str,
25
+ code: str,
26
+ slug: str,
27
+ git_service: str,
28
+ token: str,
29
+ fail_on_error: bool,
30
+ ):
31
+ with sentry_sdk.start_transaction(op="task", name="Get Report Results"):
32
+ with sentry_sdk.start_span(name="get_report_results"):
33
+ enterprise_url = ctx.obj.get("enterprise_url")
34
+ args = get_cli_args(ctx)
35
+ logger.debug(
36
+ "Getting report results",
37
+ extra=dict(
38
+ extra_log_attributes=args,
39
+ ),
40
+ )
41
+ encoded_slug = encode_slug(slug)
42
+ send_reports_result_get_request(
43
+ commit_sha=commit_sha,
44
+ report_code=code,
45
+ encoded_slug=encoded_slug,
46
+ service=git_service,
47
+ token=token,
48
+ enterprise_url=enterprise_url,
49
+ fail_on_error=fail_on_error,
50
+ )
@@ -0,0 +1,269 @@
1
+ import json
2
+ import logging
3
+ import pathlib
4
+ from typing import Dict, List, Optional
5
+
6
+ import click
7
+ import sentry_sdk
8
+
9
+ from codecov_cli.fallbacks import CodecovOption, FallbackFieldEnum
10
+ from codecov_cli.helpers.args import get_cli_args
11
+ from codecov_cli.helpers.validators import validate_commit_sha
12
+ from codecov_cli.runners import get_runner
13
+ from codecov_cli.runners.types import (
14
+ LabelAnalysisRequestResult,
15
+ LabelAnalysisRunnerInterface,
16
+ )
17
+ from codecov_cli.types import CommandContext
18
+
19
+ logger = logging.getLogger("codecovcli")
20
+
21
+
22
+ @click.command(hidden=True, deprecated=True)
23
+ @click.option(
24
+ "--token",
25
+ required=True,
26
+ envvar="CODECOV_STATIC_TOKEN",
27
+ help="The static analysis token (NOT the same token as upload)",
28
+ )
29
+ @click.option(
30
+ "--head-sha",
31
+ "head_commit_sha",
32
+ help="Commit SHA (with 40 chars)",
33
+ cls=CodecovOption,
34
+ fallback_field=FallbackFieldEnum.commit_sha,
35
+ callback=validate_commit_sha,
36
+ required=True,
37
+ )
38
+ @click.option(
39
+ "--base-sha",
40
+ "base_commit_sha",
41
+ help="Commit SHA (with 40 chars)",
42
+ cls=CodecovOption,
43
+ callback=validate_commit_sha,
44
+ required=True,
45
+ )
46
+ @click.option(
47
+ "--runner-name", "--runner", "runner_name", help="Runner to use", default="pytest"
48
+ )
49
+ @click.option(
50
+ "--max-wait-time",
51
+ "max_wait_time",
52
+ help="Max time (in seconds) to wait for the label analysis result before falling back to running all tests. Default is to wait forever.",
53
+ default=None,
54
+ type=int,
55
+ )
56
+ @click.option(
57
+ "--dry-run",
58
+ "dry_run",
59
+ help=(
60
+ "Print list of tests to run AND tests skipped AND options that need to be added to the test runner to stdout. "
61
+ + "Choose format with --dry-run-format option. Default is JSON. "
62
+ ),
63
+ is_flag=True,
64
+ )
65
+ @click.option(
66
+ "--dry-run-format",
67
+ "dry_run_format",
68
+ type=click.Choice(["json", "space-separated-list"]),
69
+ help="Format in which --dry-run data is printed. Default is JSON.",
70
+ default="json",
71
+ )
72
+ @click.option(
73
+ "--runner-param",
74
+ "runner_params",
75
+ multiple=True,
76
+ )
77
+ @click.pass_context
78
+ def label_analysis(
79
+ ctx: CommandContext,
80
+ token: str,
81
+ head_commit_sha: str,
82
+ base_commit_sha: str,
83
+ runner_name: str,
84
+ max_wait_time: str,
85
+ dry_run: bool,
86
+ dry_run_format: str,
87
+ runner_params: List[str],
88
+ ):
89
+ with sentry_sdk.start_transaction(op="task", name="Label Analysis"):
90
+ with sentry_sdk.start_span(name="labelanalysis"):
91
+ args = get_cli_args(ctx)
92
+ logger.debug(
93
+ "Starting label analysis",
94
+ extra=dict(
95
+ extra_log_attributes=args,
96
+ ),
97
+ )
98
+ if head_commit_sha == base_commit_sha:
99
+ logger.error(
100
+ "Base and head sha can't be the same",
101
+ extra=dict(
102
+ extra_log_attributes=dict(
103
+ head_commit_sha=head_commit_sha,
104
+ base_commit_sha=base_commit_sha,
105
+ )
106
+ ),
107
+ )
108
+ raise click.ClickException(
109
+ click.style("Unable to run label analysis", fg="red")
110
+ )
111
+
112
+ codecov_yaml = ctx.obj["codecov_yaml"] or {}
113
+ cli_config = codecov_yaml.get("cli", {})
114
+ # Raises error if no runner is found
115
+ parsed_runner_params = _parse_runner_params(runner_params)
116
+ runner = get_runner(cli_config, runner_name, parsed_runner_params)
117
+ logger.debug(
118
+ f"Selected runner: {runner}",
119
+ extra=dict(extra_log_attributes=dict(config=runner.params)),
120
+ )
121
+
122
+ logger.info("Collecting labels...")
123
+ requested_labels = runner.collect_tests()
124
+ logger.info(f"Collected {len(requested_labels)} test labels")
125
+ logger.debug(
126
+ "Labels collected",
127
+ extra=dict(
128
+ extra_log_attributes=dict(labels_collected=requested_labels)
129
+ ),
130
+ )
131
+
132
+ _fallback_to_collected_labels(
133
+ requested_labels,
134
+ runner,
135
+ dry_run=dry_run,
136
+ dry_run_format=dry_run_format,
137
+ fallback_reason="codecov_unavailable",
138
+ )
139
+ return
140
+
141
+
142
+ def _parse_runner_params(runner_params: List[str]) -> Dict[str, str]:
143
+ """Parses the structured list of dynamic runner params into a dictionary.
144
+ Structure is `key=value`. If value is a list make it comma-separated.
145
+ If the list item doesn't have '=' we consider it the key and set to None.
146
+
147
+ EXAMPLE:
148
+ runner_params = ['key=value', 'null_item', 'list=item1,item2,item3']
149
+ _parse_runner_params(runner_params) == {
150
+ 'key': 'value',
151
+ 'null_item': None,
152
+ 'list': ['item1', 'item2', 'item3']
153
+ }
154
+ """
155
+ final_params = {}
156
+ for param in runner_params:
157
+ # Emit warning if param is not well formatted
158
+ # Using == 0 rather than != 1 because there might be
159
+ # a good reason for the param to include '=' in the value.
160
+ if param.count("=") == 0:
161
+ logger.warning(
162
+ f"Runner param {param} is not well formatted. Setting value to None. Use '--runner-param key=value' to set value"
163
+ )
164
+ final_params[param] = None
165
+ else:
166
+ key, value = param.split("=", 1)
167
+ # For list values we need to split the list too
168
+ if "," in value:
169
+ value = value.split(",")
170
+ final_params[key] = value
171
+ return final_params
172
+
173
+
174
+ def _dry_run_json_output(
175
+ labels_to_run: set,
176
+ labels_to_skip: set,
177
+ runner_options: List[str],
178
+ fallback_reason: str = None,
179
+ ) -> None:
180
+ output_as_dict = dict(
181
+ runner_options=runner_options,
182
+ ats_tests_to_run=sorted(labels_to_run),
183
+ ats_tests_to_skip=sorted(labels_to_skip),
184
+ ats_fallback_reason=fallback_reason,
185
+ )
186
+ # ⚠️ DON'T use logger
187
+ # logger goes to stderr, we want it in stdout
188
+ click.echo(json.dumps(output_as_dict))
189
+
190
+
191
+ def _dry_run_list_output(
192
+ labels_to_run: set,
193
+ labels_to_skip: set,
194
+ runner_options: List[str],
195
+ fallback_reason: str = None,
196
+ ) -> None:
197
+ if fallback_reason:
198
+ logger.warning(f"label-analysis didn't run correctly. Error: {fallback_reason}")
199
+
200
+ to_run_line = " ".join(
201
+ sorted(map(lambda option: f"'{option}'", runner_options))
202
+ + sorted(map(lambda label: f"'{label}'", labels_to_run))
203
+ )
204
+ to_skip_line = " ".join(
205
+ sorted(map(lambda option: f"'{option}'", runner_options))
206
+ + sorted(map(lambda label: f"'{label}'", labels_to_skip))
207
+ )
208
+ # ⚠️ DON'T use logger
209
+ # logger goes to stderr, we want it in stdout
210
+ click.echo(f"TESTS_TO_RUN={to_run_line}")
211
+ click.echo(f"TESTS_TO_SKIP={to_skip_line}")
212
+
213
+
214
+ def _dry_run_output(
215
+ result: LabelAnalysisRequestResult,
216
+ runner: LabelAnalysisRunnerInterface,
217
+ dry_run_format: str,
218
+ *,
219
+ # If we have a fallback reason it means that calculating the list of tests to run
220
+ # failed at some point. So it was not a completely successful task.
221
+ fallback_reason: str = None,
222
+ ):
223
+ labels_to_run = set(
224
+ result.absent_labels + result.global_level_labels + result.present_diff_labels
225
+ )
226
+ labels_to_skip = set(result.present_report_labels) - labels_to_run
227
+
228
+ format_lookup = {
229
+ "json": _dry_run_json_output,
230
+ "space-separated-list": _dry_run_list_output,
231
+ }
232
+ # Because dry_run_format is a click.Choice we can
233
+ # be sure the value will be in the dict of choices
234
+ fn_to_use = format_lookup[dry_run_format]
235
+ fn_to_use(
236
+ labels_to_run, labels_to_skip, runner.dry_run_runner_options, fallback_reason
237
+ )
238
+
239
+
240
+ def _fallback_to_collected_labels(
241
+ collected_labels: List[str],
242
+ runner: LabelAnalysisRunnerInterface,
243
+ *,
244
+ fallback_reason: str = None,
245
+ dry_run: bool = False,
246
+ dry_run_format: Optional[pathlib.Path] = None,
247
+ ) -> dict:
248
+ logger.info("Trying to fallback on collected labels")
249
+ if collected_labels:
250
+ logger.info("Using collected labels as tests to run")
251
+ fake_response = LabelAnalysisRequestResult(
252
+ {
253
+ "present_report_labels": [],
254
+ "absent_labels": collected_labels,
255
+ "present_diff_labels": [],
256
+ "global_level_labels": [],
257
+ }
258
+ )
259
+ if not dry_run:
260
+ return runner.process_labelanalysis_result(fake_response)
261
+ else:
262
+ return _dry_run_output(
263
+ LabelAnalysisRequestResult(fake_response),
264
+ runner,
265
+ dry_run_format,
266
+ fallback_reason=fallback_reason,
267
+ )
268
+ logger.error("Cannot fallback to collected labels because no labels were collected")
269
+ raise click.ClickException("Failed to get list of labels to run")