idf-build-apps 2.5.1__tar.gz → 2.5.3__tar.gz

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 (72) hide show
  1. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/.github/workflows/test-build-idf-apps.yml +6 -1
  2. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/CHANGELOG.md +13 -0
  3. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/PKG-INFO +1 -1
  4. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/idf_build_apps/__init__.py +1 -1
  5. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/idf_build_apps/args.py +33 -5
  6. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/idf_build_apps/finder.py +1 -0
  7. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/pyproject.toml +1 -1
  8. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/setup.py +1 -1
  9. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/tests/conftest.py +2 -1
  10. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/tests/test_args.py +104 -0
  11. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/tests/test_cmd.py +29 -1
  12. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/.editorconfig +0 -0
  13. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/.git-blame-ignore-revs +0 -0
  14. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/.gitattributes +0 -0
  15. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/.github/dependabot.yml +0 -0
  16. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/.github/workflows/check-pre-commit.yml +0 -0
  17. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/.github/workflows/issue_comment.yml +0 -0
  18. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/.github/workflows/new_issues.yml +0 -0
  19. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/.github/workflows/new_prs.yml +0 -0
  20. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/.github/workflows/publish-pypi.yml +0 -0
  21. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/.github/workflows/test-build-docs.yml +0 -0
  22. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/.gitignore +0 -0
  23. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/.pre-commit-config.yaml +0 -0
  24. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/.readthedocs.yml +0 -0
  25. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/CONTRIBUTING.md +0 -0
  26. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/LICENSE +0 -0
  27. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/README.md +0 -0
  28. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/docs/_apidoc_templates/module.rst_t +0 -0
  29. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/docs/_apidoc_templates/package.rst_t +0 -0
  30. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/docs/_apidoc_templates/toc.rst_t +0 -0
  31. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/docs/_static/espressif-logo.svg +0 -0
  32. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/docs/_static/theme_overrides.css +0 -0
  33. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/docs/_templates/layout.html +0 -0
  34. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/docs/conf_common.py +0 -0
  35. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/docs/en/Makefile +0 -0
  36. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/docs/en/conf.py +0 -0
  37. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/docs/en/explanations/build.rst +0 -0
  38. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/docs/en/explanations/config_rules.rst +0 -0
  39. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/docs/en/explanations/dependency_driven_build.rst +0 -0
  40. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/docs/en/explanations/find.rst +0 -0
  41. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/docs/en/guides/1.x_to_2.x.md +0 -0
  42. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/docs/en/index.rst +0 -0
  43. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/docs/en/others/CHANGELOG.md +0 -0
  44. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/docs/en/others/CONTRIBUTING.md +0 -0
  45. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/docs/en/references/cli.rst +0 -0
  46. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/docs/en/references/config_file.md +0 -0
  47. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/docs/en/references/manifest.rst +0 -0
  48. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/idf_build_apps/__main__.py +0 -0
  49. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/idf_build_apps/app.py +0 -0
  50. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/idf_build_apps/autocompletions.py +0 -0
  51. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/idf_build_apps/constants.py +0 -0
  52. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/idf_build_apps/junit/__init__.py +0 -0
  53. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/idf_build_apps/junit/report.py +0 -0
  54. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/idf_build_apps/junit/utils.py +0 -0
  55. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/idf_build_apps/log.py +0 -0
  56. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/idf_build_apps/main.py +0 -0
  57. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/idf_build_apps/manifest/__init__.py +0 -0
  58. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/idf_build_apps/manifest/if_parser.py +0 -0
  59. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/idf_build_apps/manifest/manifest.py +0 -0
  60. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/idf_build_apps/manifest/soc_header.py +0 -0
  61. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/idf_build_apps/session_args.py +0 -0
  62. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/idf_build_apps/utils.py +0 -0
  63. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/idf_build_apps/vendors/__init__.py +0 -0
  64. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/idf_build_apps/vendors/pydantic_sources.py +0 -0
  65. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/idf_build_apps/yaml/__init__.py +0 -0
  66. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/idf_build_apps/yaml/parser.py +0 -0
  67. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/license_header.txt +0 -0
  68. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/tests/test_app.py +0 -0
  69. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/tests/test_build.py +0 -0
  70. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/tests/test_finder.py +0 -0
  71. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/tests/test_manifest.py +0 -0
  72. {idf_build_apps-2.5.1 → idf_build_apps-2.5.3}/tests/test_utils.py +0 -0
@@ -76,7 +76,12 @@ jobs:
76
76
  python -m idf_build_apps build -vv -t esp32 \
77
77
  -p $IDF_PATH/examples/get-started/hello_world \
78
78
  --size-file size_info.json
79
- pytest --cov idf_build_apps --cov-report term-missing --junit-xml report.xml
79
+ pytest --cov idf_build_apps --cov-report term-missing:skip-covered --junit-xml pytest.xml | tee pytest-coverage.txt
80
+ - name: Pytest coverage comment
81
+ uses: MishaKav/pytest-coverage-comment@main
82
+ with:
83
+ pytest-coverage-path: pytest-coverage.txt
84
+ junitxml-path: pytest.xml
80
85
 
81
86
  build-apps-on-idf-8266:
82
87
  runs-on: ubuntu-latest
@@ -2,6 +2,19 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## v2.5.3 (2024-10-04)
6
+
7
+ ### Feat
8
+
9
+ - support --manifest-filepatterns
10
+
11
+ ## v2.5.2 (2024-09-27)
12
+
13
+ ### Fix
14
+
15
+ - unset CLI argument wrongly overwrite config file settings with default value
16
+ - allow unknown fields
17
+
5
18
  ## v2.5.1 (2024-09-26)
6
19
 
7
20
  ### Fix
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: idf-build-apps
3
- Version: 2.5.1
3
+ Version: 2.5.3
4
4
  Summary: Tools for building ESP-IDF related apps.
5
5
  Author-email: Fu Hanxi <fuhanxi@espressif.com>
6
6
  Requires-Python: >=3.7
@@ -8,7 +8,7 @@ Tools for building ESP-IDF related apps.
8
8
  # ruff: noqa: E402
9
9
  # avoid circular imports
10
10
 
11
- __version__ = '2.5.1'
11
+ __version__ = '2.5.3'
12
12
 
13
13
  from .session_args import (
14
14
  SessionArgs,
@@ -3,6 +3,7 @@
3
3
 
4
4
  import argparse
5
5
  import enum
6
+ import glob
6
7
  import inspect
7
8
  import logging
8
9
  import os
@@ -117,6 +118,7 @@ class BaseArguments(BaseSettings):
117
118
  toml_file='.idf_build_apps.toml',
118
119
  pyproject_toml_table_header=('tool', 'idf-build-apps'),
119
120
  pyproject_toml_depth=sys.maxsize,
121
+ extra='ignore',
120
122
  )
121
123
 
122
124
  @classmethod
@@ -140,8 +142,9 @@ class BaseArguments(BaseSettings):
140
142
  if info.field_name and info.field_name in cls.model_fields:
141
143
  f = cls.model_fields[info.field_name]
142
144
  meta = get_meta(f)
143
- if meta and meta.validate_method and ValidateMethod.TO_LIST in meta.validate_method:
144
- return to_list(v)
145
+ if meta and meta.validate_method:
146
+ if ValidateMethod.TO_LIST in meta.validate_method:
147
+ return to_list(v)
145
148
 
146
149
  return v
147
150
 
@@ -189,6 +192,15 @@ class DependencyDrivenBuildArguments(GlobalArguments):
189
192
  validation_alias=AliasChoices('manifest_files', 'manifest_file'),
190
193
  default=None,
191
194
  )
195
+ manifest_filepatterns: t.Optional[t.List[str]] = field(
196
+ FieldMetadata(
197
+ validate_method=[ValidateMethod.TO_LIST],
198
+ nargs='+',
199
+ ),
200
+ description='space-separated list of file patterns to search for the manifest files. '
201
+ 'The matched files will be loaded as the manifest files.',
202
+ default=None,
203
+ )
192
204
  manifest_rootpath: str = field(
193
205
  None,
194
206
  description='Root path to resolve the relative paths defined in the manifest files. '
@@ -268,7 +280,7 @@ class DependencyDrivenBuildArguments(GlobalArguments):
268
280
  )
269
281
  compare_manifest_sha_filepath: t.Optional[str] = field(
270
282
  None,
271
- description='Path to the file containing the sha256 hash of the manifest rules. '
283
+ description='Path to the file containing the hash of the manifest rules. '
272
284
  'Compare the hash with the current manifest rules. '
273
285
  'All matched apps will be built if the corresponding manifest rule is modified',
274
286
  default=None,
@@ -277,6 +289,17 @@ class DependencyDrivenBuildArguments(GlobalArguments):
277
289
  def model_post_init(self, __context: Any) -> None:
278
290
  super().model_post_init(__context)
279
291
 
292
+ if self.manifest_filepatterns:
293
+ matched_paths = set()
294
+ for pat in [to_absolute_path(p, self.manifest_rootpath) for p in self.manifest_filepatterns]:
295
+ matched_paths.update(glob.glob(str(pat), recursive=True))
296
+
297
+ if matched_paths:
298
+ if self.manifest_files:
299
+ self.manifest_files.extend(matched_paths)
300
+ else:
301
+ self.manifest_files = list(matched_paths)
302
+
280
303
  Manifest.CHECK_MANIFEST_RULES = self.check_manifest_rules
281
304
  if self.manifest_files:
282
305
  App.MANIFEST = Manifest.from_files(
@@ -799,14 +822,19 @@ def add_args_to_parser(argument_cls: t.Type[BaseArguments], parser: argparse.Arg
799
822
  kwargs['required'] = True
800
823
  if f_meta.action:
801
824
  kwargs['action'] = f_meta.action
825
+ # to make the CLI override config file work
826
+ if f_meta.action == 'store_true':
827
+ kwargs['default'] = None
828
+
802
829
  if f_meta.nargs:
803
830
  kwargs['nargs'] = f_meta.nargs
804
831
  if f_meta.choices:
805
832
  kwargs['choices'] = f_meta.choices
806
833
  if f_meta.default:
807
834
  kwargs['default'] = f_meta.default
808
- if 'default' not in kwargs:
809
- kwargs['default'] = f.default
835
+
836
+ # here in CLI arguments, don't set the default to field.default
837
+ # otherwise it will override the config file settings
810
838
 
811
839
  parser.add_argument(
812
840
  *names,
@@ -33,6 +33,7 @@ def _get_apps_from_path(
33
33
  app_cls: t.Type[App] = CMakeApp,
34
34
  args: FindArguments,
35
35
  ) -> t.List[App]:
36
+ # trigger test
36
37
  def _validate_app(_app: App) -> bool:
37
38
  if target not in _app.supported_targets:
38
39
  LOGGER.debug('=> Ignored. %s only supports targets: %s', _app, ', '.join(_app.supported_targets))
@@ -60,7 +60,7 @@ idf-build-apps = "idf_build_apps:main.main"
60
60
 
61
61
  [tool.commitizen]
62
62
  name = "cz_conventional_commits"
63
- version = "2.5.1"
63
+ version = "2.5.3"
64
64
  tag_format = "v$version"
65
65
  version_files = [
66
66
  "idf_build_apps/__init__.py",
@@ -35,7 +35,7 @@ entry_points = \
35
35
  {'console_scripts': ['idf-build-apps = idf_build_apps:main.main']}
36
36
 
37
37
  setup(name='idf-build-apps',
38
- version='2.5.1',
38
+ version='2.5.3',
39
39
  description='Tools for building ESP-IDF related apps.',
40
40
  author=None,
41
41
  author_email='Fu Hanxi <fuhanxi@espressif.com>',
@@ -10,13 +10,14 @@ from idf_build_apps import (
10
10
  setup_logging,
11
11
  )
12
12
  from idf_build_apps.args import apply_config_file
13
- from idf_build_apps.constants import IDF_BUILD_APPS_TOML_FN
13
+ from idf_build_apps.constants import IDF_BUILD_APPS_TOML_FN, SUPPORTED_TARGETS
14
14
  from idf_build_apps.manifest.manifest import FolderRule
15
15
 
16
16
 
17
17
  @pytest.fixture(autouse=True)
18
18
  def clean_cls_attr(tmp_path):
19
19
  App.MANIFEST = None
20
+ FolderRule.DEFAULT_BUILD_TARGETS = SUPPORTED_TARGETS
20
21
  idf_build_apps.SESSION_ARGS.clean()
21
22
  apply_config_file(IDF_BUILD_APPS_TOML_FN)
22
23
  os.chdir(tmp_path)
@@ -1,7 +1,11 @@
1
1
  # SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
2
2
  # SPDX-License-Identifier: Apache-2.0
3
+ from xml.etree import ElementTree
3
4
 
4
5
  import pytest
6
+ from conftest import (
7
+ create_project,
8
+ )
5
9
 
6
10
  from idf_build_apps import App
7
11
  from idf_build_apps.args import (
@@ -190,3 +194,103 @@ class TestIgnoreWarningFile:
190
194
  assert len(App.IGNORE_WARNS_REGEXES) == 2
191
195
  assert App.IGNORE_WARNS_REGEXES[0].pattern == 'warning:xxx'
192
196
  assert App.IGNORE_WARNS_REGEXES[1].pattern == 'warning:yyy'
197
+
198
+ def test_ignore_extra_fields(self):
199
+ with open(IDF_BUILD_APPS_TOML_FN, 'w') as fw:
200
+ fw.write("""dry_run = true""")
201
+
202
+ args = FindArguments()
203
+ assert not hasattr(args, 'dry_run')
204
+
205
+ def test_config_file(self, tmp_path, monkeypatch):
206
+ create_project('foo', tmp_path)
207
+
208
+ with open(IDF_BUILD_APPS_TOML_FN, 'w') as fw:
209
+ fw.write("""paths = ["foo"]
210
+ target = "esp32"
211
+ build_dir = "build_@t"
212
+ junitxml = "test.xml"
213
+ keep_going = true
214
+ """)
215
+
216
+ # test basic config
217
+ with monkeypatch.context() as m:
218
+ m.setenv('PATH', 'foo') # let build fail
219
+ m.setattr('sys.argv', ['idf-build-apps', 'build'])
220
+ with pytest.raises(SystemExit):
221
+ main()
222
+
223
+ with open('test.xml') as f:
224
+ xml = ElementTree.fromstring(f.read())
225
+ test_suite = xml.findall('testsuite')[0]
226
+ assert test_suite.attrib['failures'] == '1'
227
+ assert test_suite.attrib['errors'] == '0'
228
+ assert test_suite.attrib['skipped'] == '0'
229
+ assert test_suite.findall('testcase')[0].attrib['name'] == 'foo/build_esp32'
230
+
231
+ # test cli overrides config
232
+ with monkeypatch.context() as m:
233
+ m.setenv('PATH', 'foo') # let build fail
234
+ m.setattr('sys.argv', ['idf-build-apps', 'build', '--build-dir', 'build_hi_@t'])
235
+ with pytest.raises(SystemExit):
236
+ main()
237
+
238
+ with open('test.xml') as f:
239
+ xml = ElementTree.fromstring(f.read())
240
+ test_suite = xml.findall('testsuite')[0]
241
+ assert test_suite.attrib['failures'] == '1'
242
+ assert test_suite.attrib['errors'] == '0'
243
+ assert test_suite.attrib['skipped'] == '0'
244
+ assert test_suite.findall('testcase')[0].attrib['name'] == 'foo/build_hi_esp32'
245
+
246
+ # test cli action_true
247
+ with monkeypatch.context() as m:
248
+ m.setattr('sys.argv', ['idf-build-apps', 'build', '--dry-run'])
249
+ main()
250
+
251
+ with open('test.xml') as f:
252
+ xml = ElementTree.fromstring(f.read())
253
+ test_suite = xml.findall('testsuite')[0]
254
+ assert test_suite.attrib['failures'] == '0'
255
+ assert test_suite.attrib['errors'] == '0'
256
+ assert test_suite.attrib['skipped'] == '1'
257
+ assert test_suite.findall('testcase')[0].attrib['name'] == 'foo/build_esp32'
258
+
259
+ # test config store_true set to true
260
+ with open(IDF_BUILD_APPS_TOML_FN, 'a') as fw:
261
+ fw.write('\ndry_run = true\n')
262
+
263
+ with monkeypatch.context() as m:
264
+ m.setattr('sys.argv', ['idf-build-apps', 'build'])
265
+ main()
266
+
267
+ with open('test.xml') as f:
268
+ xml = ElementTree.fromstring(f.read())
269
+ test_suite = xml.findall('testsuite')[0]
270
+ assert test_suite.attrib['failures'] == '0'
271
+ assert test_suite.attrib['errors'] == '0'
272
+ assert test_suite.attrib['skipped'] == '1'
273
+ assert test_suite.findall('testcase')[0].attrib['name'] == 'foo/build_esp32'
274
+
275
+ # test config store_true set to false, but CLI set to true
276
+ with open(IDF_BUILD_APPS_TOML_FN, 'w') as fw:
277
+ fw.write("""paths = ["foo"]
278
+ build_dir = "build_@t"
279
+ junitxml = "test.xml"
280
+ dry_run = false
281
+ """)
282
+
283
+ with monkeypatch.context() as m:
284
+ m.setattr(
285
+ 'sys.argv', ['idf-build-apps', 'build', '--default-build-targets', 'esp32', 'esp32s2', '--dry-run']
286
+ )
287
+ main()
288
+
289
+ with open('test.xml') as f:
290
+ xml = ElementTree.fromstring(f.read())
291
+ test_suite = xml.findall('testsuite')[0]
292
+ assert test_suite.attrib['failures'] == '0'
293
+ assert test_suite.attrib['errors'] == '0'
294
+ assert test_suite.attrib['skipped'] == '2'
295
+ assert test_suite.findall('testcase')[0].attrib['name'] == 'foo/build_esp32'
296
+ assert test_suite.findall('testcase')[1].attrib['name'] == 'foo/build_esp32s2'
@@ -1,6 +1,6 @@
1
1
  # SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
2
2
  # SPDX-License-Identifier: Apache-2.0
3
-
3
+ import os
4
4
  import sys
5
5
  from pathlib import Path
6
6
 
@@ -67,3 +67,31 @@ foobar:
67
67
  f'foo:{sha_of_enable_only_esp32}\n'
68
68
  f'foobar:{sha_of_enable_esp32_or_esp32s2}\n'
69
69
  )
70
+
71
+
72
+ def test_manifest_patterns(tmp_path, monkeypatch, capsys):
73
+ manifest = tmp_path / 'manifest.yml'
74
+ manifest.write_text(
75
+ """foo:
76
+ enable:
77
+ - if: IDF_TARGET == "esp32"
78
+ bar:
79
+ enable:
80
+ - if: IDF_TARGET == "esp32"
81
+ """
82
+ )
83
+
84
+ with pytest.raises(SystemExit) as e:
85
+ with monkeypatch.context() as m:
86
+ m.setattr(
87
+ sys,
88
+ 'argv',
89
+ ['idf-build-apps', 'find', '--manifest-filepatterns', '**.whatever', '**/manifest.yml', '-vv'],
90
+ )
91
+ main()
92
+ assert e.retcode == 0
93
+
94
+ _, err = capsys.readouterr()
95
+ assert f'Loading manifest file {os.path.join(tmp_path, "manifest.yml")}' in err
96
+ assert f'"{os.path.join(tmp_path, "foo")}" does not exist' in err
97
+ assert f'"{os.path.join(tmp_path, "bar")}" does not exist' in err
File without changes
File without changes