File: test_integration.py

package info (click to toggle)
setuptools-scm 9.2.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,108 kB
  • sloc: python: 7,068; sh: 18; makefile: 7
file content (715 lines) | stat: -rw-r--r-- 25,007 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
from __future__ import annotations

import importlib.metadata
import logging
import re
import subprocess
import sys
import textwrap

from pathlib import Path
from typing import TYPE_CHECKING
from typing import Any

import pytest

from packaging.version import Version

from setuptools_scm._integration import setuptools as setuptools_integration
from setuptools_scm._integration.pyproject_reading import PyProjectData
from setuptools_scm._integration.setup_cfg import SetuptoolsBasicData
from setuptools_scm._integration.setup_cfg import read_setup_cfg
from setuptools_scm._requirement_cls import extract_package_name

if TYPE_CHECKING:
    import setuptools

from setuptools_scm import Configuration
from setuptools_scm._integration.setuptools import _warn_on_old_setuptools
from setuptools_scm._overrides import PRETEND_KEY
from setuptools_scm._overrides import PRETEND_KEY_NAMED
from setuptools_scm._run_cmd import run

from .wd_wrapper import WorkDir

c = Configuration()


@pytest.fixture
def wd(wd: WorkDir) -> WorkDir:
    wd("git init")
    wd("git config user.email test@example.com")
    wd('git config user.name "a test"')
    wd.add_command = "git add ."
    wd.commit_command = "git commit -m test-{reason}"
    return wd


def test_pyproject_support(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    monkeypatch.delenv("SETUPTOOLS_SCM_DEBUG")
    pkg = tmp_path / "package"
    pkg.mkdir()
    pkg.joinpath("pyproject.toml").write_text(
        textwrap.dedent(
            """
            [tool.setuptools_scm]
            fallback_version = "12.34"
            [project]
            name = "foo"
            description = "Factory ⸻ A code generator 🏭"
            authors = [{name = "Łukasz Langa"}]
            dynamic = ["version"]
            """
        ),
        encoding="utf-8",
    )
    pkg.joinpath("setup.py").write_text(
        "__import__('setuptools').setup()", encoding="utf-8"
    )
    res = run([sys.executable, "setup.py", "--version"], pkg)
    assert res.stdout == "12.34"


def test_pretend_version(monkeypatch: pytest.MonkeyPatch, wd: WorkDir) -> None:
    monkeypatch.setenv(PRETEND_KEY, "1.0.0")

    assert wd.get_version() == "1.0.0"
    assert wd.get_version(dist_name="ignored") == "1.0.0"


def test_pretend_version_named(monkeypatch: pytest.MonkeyPatch, wd: WorkDir) -> None:
    monkeypatch.setenv(PRETEND_KEY_NAMED.format(name="test".upper()), "1.0.0")
    monkeypatch.setenv(PRETEND_KEY_NAMED.format(name="test2".upper()), "2.0.0")
    assert wd.get_version(dist_name="test") == "1.0.0"
    assert wd.get_version(dist_name="test2") == "2.0.0"


def test_pretend_version_name_takes_precedence(
    monkeypatch: pytest.MonkeyPatch, wd: WorkDir
) -> None:
    monkeypatch.setenv(PRETEND_KEY_NAMED.format(name="test".upper()), "1.0.0")
    monkeypatch.setenv(PRETEND_KEY, "2.0.0")
    assert wd.get_version(dist_name="test") == "1.0.0"


def test_pretend_version_rejects_invalid_string(
    monkeypatch: pytest.MonkeyPatch, wd: WorkDir
) -> None:
    """Test that invalid pretend versions raise errors and bubble up."""
    monkeypatch.setenv(PRETEND_KEY, "dummy")

    # With strict validation, invalid pretend versions should raise errors
    with pytest.raises(Exception, match=r".*dummy.*"):
        wd.get_version(write_to="test.py")


def test_pretend_metadata_with_version(
    monkeypatch: pytest.MonkeyPatch, wd: WorkDir
) -> None:
    """Test pretend metadata overrides work with pretend version."""
    from setuptools_scm._overrides import PRETEND_METADATA_KEY

    monkeypatch.setenv(PRETEND_KEY, "1.2.3.dev4+g1337beef")
    monkeypatch.setenv(PRETEND_METADATA_KEY, '{node="g1337beef", distance=4}')

    version = wd.get_version()
    assert version == "1.2.3.dev4+g1337beef"

    # Test version file template functionality
    wd("mkdir -p src")
    version_file_content = """
version = '{version}'
major = {version_tuple[0]}
minor = {version_tuple[1]}
patch = {version_tuple[2]}
commit_hash = '{scm_version.short_node}'
num_commit = {scm_version.distance}
"""  # noqa: RUF027
    # Use write_to with template to create version file
    version = wd.get_version(
        write_to="src/version.py", write_to_template=version_file_content
    )

    content = (wd.cwd / "src/version.py").read_text(encoding="utf-8")
    assert "commit_hash = 'g1337beef'" in content
    assert "num_commit = 4" in content


def test_pretend_metadata_named(monkeypatch: pytest.MonkeyPatch, wd: WorkDir) -> None:
    """Test pretend metadata with named package support."""
    from setuptools_scm._overrides import PRETEND_METADATA_KEY_NAMED

    monkeypatch.setenv(
        PRETEND_KEY_NAMED.format(name="test".upper()), "1.2.3.dev5+gabcdef12"
    )
    monkeypatch.setenv(
        PRETEND_METADATA_KEY_NAMED.format(name="test".upper()),
        '{node="gabcdef12", distance=5, dirty=true}',
    )

    version = wd.get_version(dist_name="test")
    assert version == "1.2.3.dev5+gabcdef12"


def test_pretend_metadata_without_version_warns(
    monkeypatch: pytest.MonkeyPatch, wd: WorkDir, caplog: pytest.LogCaptureFixture
) -> None:
    """Test that pretend metadata without any base version logs a warning."""
    from setuptools_scm._overrides import PRETEND_METADATA_KEY

    # Only set metadata, no version - but there will be a git repo so there will be a base version
    # Let's create an empty git repo without commits to truly have no base version
    monkeypatch.setenv(PRETEND_METADATA_KEY, '{node="g1234567", distance=2}')

    with caplog.at_level(logging.WARNING):
        version = wd.get_version()
        assert version is not None

    # In this case, metadata was applied to a fallback version, so no warning about missing base


def test_pretend_metadata_with_scm_version(
    wd: WorkDir, monkeypatch: pytest.MonkeyPatch
) -> None:
    """Test that pretend metadata works with actual SCM-detected version."""
    from setuptools_scm._overrides import PRETEND_METADATA_KEY

    # Set up a git repo with a tag so we have a base version
    wd("git init")
    wd("git config user.name test")
    wd("git config user.email test@example.com")
    wd.write("file.txt", "content")
    wd("git add file.txt")
    wd("git commit -m 'initial'")
    wd("git tag v1.0.0")

    # Now add metadata overrides
    monkeypatch.setenv(PRETEND_METADATA_KEY, '{node="gcustom123", distance=7}')

    # Test that the metadata gets applied to the actual SCM version
    version = wd.get_version()
    # The version becomes 1.0.1.dev7+gcustom123 due to version scheme and metadata overrides
    assert "1.0.1.dev7+gcustom123" == version

    # Test version file to see if metadata was applied
    wd("mkdir -p src")
    version_file_content = """
version = '{version}'
commit_hash = '{scm_version.short_node}'
num_commit = {scm_version.distance}
"""  # noqa: RUF027
    version = wd.get_version(
        write_to="src/version.py", write_to_template=version_file_content
    )

    content = (wd.cwd / "src/version.py").read_text(encoding="utf-8")
    assert "commit_hash = 'gcustom123'" in content
    assert "num_commit = 7" in content


def test_pretend_metadata_type_conversion(
    monkeypatch: pytest.MonkeyPatch, wd: WorkDir
) -> None:
    """Test that pretend metadata properly uses TOML native types."""
    from setuptools_scm._overrides import PRETEND_METADATA_KEY

    monkeypatch.setenv(PRETEND_KEY, "2.0.0")
    monkeypatch.setenv(
        PRETEND_METADATA_KEY,
        '{distance=10, dirty=true, node="gfedcba98", branch="feature-branch"}',
    )

    version = wd.get_version()
    # The version should be formatted properly with the metadata
    assert "2.0.0" in version


def test_pretend_metadata_invalid_fields_filtered(
    monkeypatch: pytest.MonkeyPatch, wd: WorkDir, caplog: pytest.LogCaptureFixture
) -> None:
    """Test that invalid metadata fields are filtered out with a warning."""
    from setuptools_scm._overrides import PRETEND_METADATA_KEY

    monkeypatch.setenv(PRETEND_KEY, "1.0.0")
    monkeypatch.setenv(
        PRETEND_METADATA_KEY,
        '{node="g123456", distance=3, invalid_field="should_be_ignored", another_bad_field=42}',
    )

    with caplog.at_level(logging.WARNING):
        version = wd.get_version()
        assert version == "1.0.0"

    assert "Invalid metadata fields in pretend metadata" in caplog.text
    assert "invalid_field" in caplog.text
    assert "another_bad_field" in caplog.text


def test_pretend_metadata_date_parsing(
    monkeypatch: pytest.MonkeyPatch, wd: WorkDir
) -> None:
    """Test that TOML date values work in pretend metadata."""
    from setuptools_scm._overrides import PRETEND_METADATA_KEY

    monkeypatch.setenv(PRETEND_KEY, "1.5.0")
    monkeypatch.setenv(
        PRETEND_METADATA_KEY, '{node="g987654", distance=7, node_date=2024-01-15}'
    )

    version = wd.get_version()
    assert version == "1.5.0"


def test_pretend_metadata_invalid_toml_error(
    monkeypatch: pytest.MonkeyPatch, wd: WorkDir, caplog: pytest.LogCaptureFixture
) -> None:
    """Test that invalid TOML in pretend metadata logs an error."""
    from setuptools_scm._overrides import PRETEND_METADATA_KEY

    monkeypatch.setenv(PRETEND_KEY, "1.0.0")
    monkeypatch.setenv(PRETEND_METADATA_KEY, "{invalid toml syntax here}")

    with caplog.at_level(logging.ERROR):
        version = wd.get_version()
        # Should fall back to basic pretend version
        assert version == "1.0.0"

    assert "Failed to parse pretend metadata" in caplog.text


def test_git_tag_with_local_build_data_preserved(wd: WorkDir) -> None:
    """Test that git tags containing local build data are preserved in final version."""
    wd.commit_testfile()

    # Create a git tag that includes local build data
    # This simulates a CI system that creates tags with build metadata
    wd("git tag 1.0.0+build.123")

    # The version should preserve the build metadata from the tag
    version = wd.get_version()

    # Validate it's a proper PEP 440 version
    parsed_version = Version(version)
    assert str(parsed_version) == version, (
        f"Version should parse correctly as PEP 440: {version}"
    )

    assert version == "1.0.0+build.123", (
        f"Expected build metadata preserved, got {version}"
    )

    # Validate the local part is correct
    assert parsed_version.local == "build.123", (
        f"Expected local part 'build.123', got {parsed_version.local}"
    )


def test_git_tag_with_commit_hash_preserved(wd: WorkDir) -> None:
    """Test that git tags with commit hash data are preserved."""
    wd.commit_testfile()

    # Create a git tag that includes commit hash metadata
    wd("git tag 2.0.0+sha.abcd1234")

    # The version should preserve the commit hash from the tag
    version = wd.get_version()

    # Validate it's a proper PEP 440 version
    parsed_version = Version(version)
    assert str(parsed_version) == version, (
        f"Version should parse correctly as PEP 440: {version}"
    )

    assert version == "2.0.0+sha.abcd1234"

    # Validate the local part is correct
    assert parsed_version.local == "sha.abcd1234", (
        f"Expected local part 'sha.abcd1234', got {parsed_version.local}"
    )


def test_git_tag_with_local_build_data_preserved_dirty_workdir(wd: WorkDir) -> None:
    """Test that git tags with local build data are preserved even with dirty working directory."""
    wd.commit_testfile()

    # Create a git tag that includes local build data
    wd("git tag 1.5.0+build.456")

    # Make working directory dirty
    wd.write("modified_file.txt", "some changes")

    # The version should preserve the build metadata from the tag
    # even when working directory is dirty
    version = wd.get_version()

    # Validate it's a proper PEP 440 version
    parsed_version = Version(version)
    assert str(parsed_version) == version, (
        f"Version should parse correctly as PEP 440: {version}"
    )

    assert version == "1.5.0+build.456", (
        f"Expected build metadata preserved with dirty workdir, got {version}"
    )

    # Validate the local part is correct
    assert parsed_version.local == "build.456", (
        f"Expected local part 'build.456', got {parsed_version.local}"
    )


def test_git_tag_with_local_build_data_preserved_with_distance(wd: WorkDir) -> None:
    """Test that git tags with local build data are preserved with distance."""
    wd.commit_testfile()

    # Create a git tag that includes local build data
    wd("git tag 3.0.0+ci.789")

    # Add another commit after the tag to create distance
    wd.commit_testfile("after-tag")

    # The version should use version scheme for distance but preserve original tag's build data
    version = wd.get_version()

    # Validate it's a proper PEP 440 version
    parsed_version = Version(version)
    assert str(parsed_version) == version, (
        f"Version should parse correctly as PEP 440: {version}"
    )

    # Tag local data should be preserved and combined with SCM data
    assert version.startswith("3.0.1.dev1"), (
        f"Expected dev version with distance, got {version}"
    )

    # Use regex to validate the version format with both tag build data and SCM node data
    # Expected format: 3.0.1.dev1+ci.789.g<commit_hash>
    version_pattern = r"^3\.0\.1\.dev1\+ci\.789\.g[a-f0-9]+$"
    assert re.match(version_pattern, version), (
        f"Version should match pattern {version_pattern}, got {version}"
    )

    # The original tag's local data (+ci.789) should be preserved and combined with SCM data
    assert "+ci.789" in version, f"Tag local data should be preserved, got {version}"

    # Validate the local part contains both tag and SCM node information
    assert parsed_version.local is not None, (
        f"Expected local version part, got {parsed_version.local}"
    )
    assert "ci.789" in parsed_version.local, (
        f"Expected local part to contain tag data 'ci.789', got {parsed_version.local}"
    )
    assert "g" in parsed_version.local, (
        f"Expected local part to contain SCM node data 'g...', got {parsed_version.local}"
    )

    # Note: This test verifies that local build data from tags is preserved and combined
    # with SCM data when there's distance, which is the desired behavior for issue 1019.


def testwarn_on_broken_setuptools() -> None:
    _warn_on_old_setuptools("61")
    with pytest.warns(RuntimeWarning, match="ERROR: setuptools==60"):
        _warn_on_old_setuptools("60")


@pytest.mark.issue(611)
def test_distribution_provides_extras() -> None:
    from importlib.metadata import distribution

    dist = distribution("setuptools_scm")
    pe: list[str] = dist.metadata.get_all("Provides-Extra", [])
    assert sorted(pe) == ["rich", "simple", "toml"]


@pytest.mark.issue(760)
def test_unicode_in_setup_cfg(tmp_path: Path) -> None:
    cfg = tmp_path / "setup.cfg"
    cfg.write_text(
        textwrap.dedent(
            """
            [metadata]
            name = configparser
            author = Łukasz Langa
            """
        ),
        encoding="utf-8",
    )
    from setuptools_scm._integration.setup_cfg import read_setup_cfg

    name = read_setup_cfg(cfg).name
    assert name == "configparser"

    # also ensure we can parse a version if present (legacy projects)
    cfg.write_text(
        textwrap.dedent(
            """
            [metadata]
            name = configparser
            version = 1.2.3
            """
        ),
        encoding="utf-8",
    )

    data = read_setup_cfg(cfg)
    assert isinstance(data, SetuptoolsBasicData)
    assert data.name == "configparser"
    assert data.version == "1.2.3"


@pytest.mark.issue(1216)
def test_setup_cfg_dynamic_version_warns_and_ignores(tmp_path: Path) -> None:
    cfg = tmp_path / "setup.cfg"
    cfg.write_text(
        textwrap.dedent(
            """
            [metadata]
            name = example-broken
            version = attr: example_broken.__version__
            """
        ),
        encoding="utf-8",
    )

    with pytest.warns(
        UserWarning,
        match=r"setup\.cfg: at \[metadata\]",
    ):
        legacy_data = read_setup_cfg(cfg)

    assert legacy_data.version is None


def test_setup_cfg_version_prevents_inference_version_keyword(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    # Legacy project setup - we construct the data directly since files are not read anyway
    monkeypatch.chdir(tmp_path)

    dist = create_clean_distribution("legacy-proj")

    # Using keyword should detect an existing version via legacy data and avoid inferring
    from setuptools_scm._integration import setuptools as setuptools_integration
    from setuptools_scm._integration.pyproject_reading import PyProjectData
    from setuptools_scm._integration.setup_cfg import SetuptoolsBasicData

    # Construct PyProjectData directly without requiring build backend inference
    pyproject_data = PyProjectData.for_testing(
        is_required=False,  # setuptools-scm not required
        section_present=False,  # no [tool.setuptools_scm] section
        project_present=False,  # no [project] section
    )

    # Construct legacy data with version from setup.cfg
    legacy_data = SetuptoolsBasicData(
        path=tmp_path / "setup.cfg", name="legacy-proj", version="0.9.0"
    )

    with pytest.warns(UserWarning, match="version of legacy-proj already set"):
        setuptools_integration.version_keyword(
            dist,
            "use_scm_version",
            True,
            _given_pyproject_data=pyproject_data,
            _given_legacy_data=legacy_data,
        )

    # setuptools_scm should not set a version when setup.cfg already provided one
    assert dist.metadata.version is None


def test_setuptools_version_keyword_ensures_regex(
    wd: WorkDir,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    wd.commit_testfile("test")
    wd("git tag 1.0")
    monkeypatch.chdir(wd.cwd)

    dist = create_clean_distribution("test")
    setuptools_integration.version_keyword(
        dist, "use_scm_version", {"tag_regex": "(1.0)"}
    )
    assert dist.metadata.version == "1.0"


@pytest.mark.parametrize(
    "ep_name", ["setuptools_scm.parse_scm", "setuptools_scm.parse_scm_fallback"]
)
def test_git_archival_plugin_ignored(tmp_path: Path, ep_name: str) -> None:
    tmp_path.joinpath(".git_archival.txt").write_text("broken", encoding="utf-8")
    try:
        dist = importlib.metadata.distribution("setuptools_scm_git_archive")
    except importlib.metadata.PackageNotFoundError:
        pytest.skip("setuptools_scm_git_archive not installed")
    else:
        print(dist.metadata["Name"], dist.version)
    from setuptools_scm.discover import iter_matching_entrypoints

    found = list(iter_matching_entrypoints(tmp_path, config=c, entrypoint=ep_name))
    imports = [item.value for item in found]
    assert "setuptools_scm_git_archive:parse" not in imports


@pytest.mark.parametrize("base_name", ["setuptools_scm", "setuptools-scm"])
@pytest.mark.parametrize(
    "requirements",
    ["", ">=8", "[toml]>=7", "~=9.0", "[rich,toml]>=8"],
    ids=["empty", "version", "extras", "fuzzy", "multiple-extras"],
)
def test_extract_package_name(base_name: str, requirements: str) -> None:
    """Test the _extract_package_name helper function"""
    assert extract_package_name(f"{base_name}{requirements}") == "setuptools-scm"


# Helper function for creating and managing distribution objects
def create_clean_distribution(name: str) -> setuptools.Distribution:
    """Create a clean distribution object without any setuptools_scm effects.

    This function creates a new setuptools Distribution and ensures it's completely
    clean from any previous setuptools_scm version inference effects, including:
    - Clearing any existing version
    - Removing the _setuptools_scm_version_set_by_infer flag
    """
    import setuptools

    dist = setuptools.Distribution({"name": name})

    # Clean all setuptools_scm effects
    dist.metadata.version = None
    if hasattr(dist, "_setuptools_scm_version_set_by_infer"):
        delattr(dist, "_setuptools_scm_version_set_by_infer")

    return dist


def version_keyword_default(
    dist: setuptools.Distribution, pyproject_data: PyProjectData | None = None
) -> None:
    """Helper to call version_keyword with default config and return the result."""

    setuptools_integration.version_keyword(
        dist, "use_scm_version", True, _given_pyproject_data=pyproject_data
    )


def version_keyword_calver(
    dist: setuptools.Distribution, pyproject_data: PyProjectData | None = None
) -> None:
    """Helper to call version_keyword with calver-by-date scheme and return the result."""

    setuptools_integration.version_keyword(
        dist,
        "use_scm_version",
        {"version_scheme": "calver-by-date"},
        _given_pyproject_data=pyproject_data,
    )


def infer_version_with_data(
    dist: setuptools.Distribution, pyproject_data: PyProjectData | None = None
) -> None:
    """Helper to call infer_version with pyproject data."""

    setuptools_integration.infer_version(dist, _given_pyproject_data=pyproject_data)


@pytest.mark.issue("https://github.com/pypa/setuptools_scm/issues/1022")
@pytest.mark.filterwarnings("ignore:version of .* already set:UserWarning")
@pytest.mark.filterwarnings(
    "ignore:.* does not correspond to a valid versioning date.*:UserWarning"
)
@pytest.mark.parametrize(
    ("first_integration", "second_integration", "expected_final_version"),
    [
        # infer_version and version_keyword can be called in either order
        (infer_version_with_data, version_keyword_default, "1.0.1.dev1"),
        (infer_version_with_data, version_keyword_calver, "9.2.13.0.dev1"),
        (version_keyword_default, infer_version_with_data, "1.0.1.dev1"),
        (version_keyword_calver, infer_version_with_data, "9.2.13.0.dev1"),
    ],
)
def test_integration_function_call_order(
    wd: WorkDir,
    monkeypatch: pytest.MonkeyPatch,
    first_integration: Any,
    second_integration: Any,
    expected_final_version: str,
) -> None:
    """Test that integration functions can be called in any order.

    version_keyword should always win when it specifies configuration, but currently doesn't.
    Some tests will fail, showing the bug.
    """
    # Set up controlled environment for deterministic versions
    monkeypatch.setenv("SOURCE_DATE_EPOCH", "1234567890")  # 2009-02-13T23:31:30+00:00
    # Override node_date to get consistent calver versions
    monkeypatch.setenv(
        "SETUPTOOLS_SCM_PRETEND_METADATA_FOR_TEST_CALL_ORDER", "{node_date=2009-02-13}"
    )

    # Set up a git repository with a tag and known commit hash
    wd.commit_testfile("test")
    wd("git tag 1.0.0")
    wd.commit_testfile("test2")  # Add another commit to get distance
    monkeypatch.chdir(wd.cwd)

    # Create PyProjectData with equivalent configuration - no file I/O!
    project_name = "test-call-order"
    pyproject_data = PyProjectData.for_testing(
        project_name=project_name,
        has_dynamic_version=True,
        project_present=True,
        section_present=True,
        local_scheme="no-local-version",
    )

    dist = create_clean_distribution(project_name)

    # Call both integration functions in order with direct data injection
    first_integration(dist, pyproject_data)
    second_integration(dist, pyproject_data)

    # Get the final version directly from the distribution
    final_version = dist.metadata.version

    # Assert the final version matches expectation
    # Some tests will fail here, demonstrating the bug where version_keyword doesn't override
    assert final_version == expected_final_version, (
        f"Expected version '{expected_final_version}' but got '{final_version}'"
    )


@pytest.mark.issue("xmlsec-regression")
def test_xmlsec_download_regression(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """Test that pip download works for xmlsec package without causing setuptools_scm regression.

    This test ensures that downloading and building xmlsec from source doesn't fail
    due to setuptools_scm issues when using --no-build-isolation.
    """
    # Set up environment with setuptools_scm debug enabled
    monkeypatch.setenv("SETUPTOOLS_SCM_DEBUG", "1")
    monkeypatch.setenv("COLUMNS", "150")

    # Run pip download command with no-binary and no-build-isolation
    try:
        subprocess.run(
            [
                *(sys.executable, "-m", "pip", "download"),
                *("--no-binary", "xmlsec"),
                "--no-build-isolation",
                "-v",
                "xmlsec==1.3.16",
            ],
            cwd=tmp_path,
            timeout=300,
            check=True,
        )
    except subprocess.CalledProcessError as e:
        pytest.fail(f"pip download failed: {e}", pytrace=False)

    # The success of the subprocess.run call above means the regression is fixed.
    # pip download succeeded without setuptools_scm causing version conflicts.