alef 0.85.15

Opinionated polyglot binding generator for Rust libraries
Documentation
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
//! Coverage for [`check_generated_uv_lock_freshness`] / [`stale_uv_lock_findings`], the uv/Python
//! sibling of the cargo/node checks. Like the node fixtures there is no path-dependency
//! indirection: the specifiers being compared live in the one `pyproject.toml` alef generated, so
//! the fixtures here only need that file and a `uv.lock` beside it. `requires_dist_map`'s
//! marker/extra filtering and `parse_pep508_requirement`/`normalize_pep503_name` are exercised
//! indirectly through every fixture here rather than unit-tested in isolation, matching how
//! `locked_node_specifiers` is covered in the node suite.

use super::*;

const UV_DIR_RELATIVE: &str = "e2e/python";
const UV_DEPENDENCY: &str = "sample-pkg";
const UV_STALE_SPEC: &str = ">=1.1.1";
const UV_FRESH_SPEC: &str = ">=1.2.0";

fn uv_dir(root: &Path) -> PathBuf {
    root.join(UV_DIR_RELATIVE)
}

/// The alef-generated e2e `pyproject.toml`, matching the shape
/// `crate::e2e::codegen::python::config::render_pyproject` emits: a `[project.dependencies]`
/// array holding a plain PEP 508 requirement string.
fn write_pyproject(root: &Path, specifier: &str) -> PathBuf {
    let dir = uv_dir(root);
    std::fs::create_dir_all(&dir).expect("create uv dir");
    let manifest = dir.join("pyproject.toml");
    std::fs::write(
        &manifest,
        format!(
            "[project]\nname = \"sample-pkg-e2e\"\nversion = \"0.0.0\"\ndependencies = \
             [\"{UV_DEPENDENCY}{specifier}\"]\n"
        ),
    )
    .expect("write pyproject.toml");
    manifest
}

/// The project-lock shape: the project's own `[[package]]` entry carries `[package.metadata]
/// requires-dist`, keyed by name off `pyproject.toml`'s own `[project.name]`.
fn write_uv_lock_project_shape(root: &Path, locked_specifier: &str) {
    std::fs::write(
        uv_dir(root).join("uv.lock"),
        format!(
            "version = 1\nrequires-python = \">=3.10\"\n\n\
             [[package]]\nname = \"sample-pkg-e2e\"\nversion = \"0.0.0\"\nsource = {{ virtual = \".\" }}\n\
             dependencies = [\n  {{ name = \"{UV_DEPENDENCY}\" }},\n]\n\n\
             [package.metadata]\nrequires-dist = [{{ name = \"{UV_DEPENDENCY}\", specifier = \
             \"{locked_specifier}\" }}]\n"
        ),
    )
    .expect("write uv.lock");
}

/// The standalone-script-lock shape: no per-package `[[package]]` entry for the project at all
/// (there is no project -- a script lock has nothing to attach `[package.metadata]` to), only a
/// top-level `[manifest] requirements` array carrying the same `{ name, specifier }` shape. This
/// is the fallback `locked_uv_requirements` must also read.
fn write_uv_lock_manifest_shape(root: &Path, locked_specifier: &str) {
    std::fs::write(
        uv_dir(root).join("uv.lock"),
        format!(
            "version = 1\nrequires-python = \">=3.10\"\n\n\
             [manifest]\nrequirements = [{{ name = \"{UV_DEPENDENCY}\", specifier = \
             \"{locked_specifier}\" }}]\n"
        ),
    )
    .expect("write uv.lock");
}

/// The regression: `pyproject.toml` was regenerated with a specifier the committed `uv.lock`
/// does not record, exactly the shape that fails `uv sync --locked` under the default frozen
/// lockfile in CI. Before this module alef reported nothing and exited 0.
#[test]
fn stale_uv_lock_findings_reports_a_specifier_mismatch() {
    let temp = tempfile::tempdir().expect("tempdir");
    let root = temp.path();
    write_pyproject(root, UV_STALE_SPEC);
    write_uv_lock_project_shape(root, UV_FRESH_SPEC);

    let findings = stale_uv_lock_findings(&uv_dir(root));

    assert_eq!(findings.len(), 1, "expected exactly one finding, got: {findings:?}");
    let finding = &findings[0];
    assert_eq!(finding.dependency, UV_DEPENDENCY);
    assert_eq!(finding.requirement, UV_STALE_SPEC);
    assert_eq!(finding.locked_requirement, UV_FRESH_SPEC);
    assert_eq!(finding.lock, uv_dir(root).join("uv.lock"));
    assert_eq!(finding.declared_in, uv_dir(root).join("pyproject.toml"));
}

/// The control that stops "always fail" from satisfying this suite: the identical fixture with
/// a lock that already records the same specifier must produce nothing at all. This is the one
/// that would NOT fail if `stale_uv_lock_findings` were reverted to always compare unconditionally
/// -- it instead catches a reversion that made the comparison unconditionally report.
#[test]
fn stale_uv_lock_findings_accepts_a_lock_that_matches() {
    let temp = tempfile::tempdir().expect("tempdir");
    let root = temp.path();
    write_pyproject(root, UV_FRESH_SPEC);
    write_uv_lock_project_shape(root, UV_FRESH_SPEC);

    let findings = stale_uv_lock_findings(&uv_dir(root));

    assert!(
        findings.is_empty(),
        "a lock matching pyproject.toml must be reported clean: {findings:?}"
    );
}

/// The one-sided rule, matching the cargo and node checks' absence rule: a dependency
/// `pyproject.toml` declares but the lock's recorded copy never mentions is not reported. The
/// lock here is non-empty (it pins an unrelated package) so this exercises the per-name lookup
/// missing, not merely an empty map short-circuiting earlier.
#[test]
fn stale_uv_lock_findings_ignores_a_dependency_absent_from_the_lock() {
    let temp = tempfile::tempdir().expect("tempdir");
    let root = temp.path();
    write_pyproject(root, UV_STALE_SPEC);
    std::fs::write(
        uv_dir(root).join("uv.lock"),
        "version = 1\nrequires-python = \">=3.10\"\n\n\
         [[package]]\nname = \"sample-pkg-e2e\"\nversion = \"0.0.0\"\nsource = { virtual = \".\" }\n\n\
         [package.metadata]\nrequires-dist = [{ name = \"other-pkg\", specifier = \">=2.0\" }]\n",
    )
    .expect("write uv.lock");

    let findings = stale_uv_lock_findings(&uv_dir(root));

    assert!(
        findings.is_empty(),
        "a package missing from the lock's recorded copy is not evidence of drift: {findings:?}"
    );
}

/// The standalone-script-lock fallback shape must be read too, not only the project shape --
/// this is the one that would fail if the `[manifest] requirements` fallback in
/// `locked_uv_requirements` were never reached (e.g. dropped, or only tried when the project
/// shape's own array is literally absent rather than merely mapping to nothing).
#[test]
fn stale_uv_lock_findings_reports_a_mismatch_in_the_manifest_requirements_fallback_shape() {
    let temp = tempfile::tempdir().expect("tempdir");
    let root = temp.path();
    write_pyproject(root, UV_STALE_SPEC);
    write_uv_lock_manifest_shape(root, UV_FRESH_SPEC);

    let findings = stale_uv_lock_findings(&uv_dir(root));

    assert_eq!(
        findings.len(),
        1,
        "expected the manifest.requirements fallback shape to be read too, got: {findings:?}"
    );
    assert_eq!(findings[0].locked_requirement, UV_FRESH_SPEC);
}

/// A dependency declared with an environment marker is conditional on something this reader
/// does not evaluate; comparing its specifier text against the lock's recorded copy would not
/// be reliable evidence of drift, so it must not be reported even when the texts do differ.
#[test]
fn stale_uv_lock_findings_ignores_a_marker_conditional_dependency() {
    let temp = tempfile::tempdir().expect("tempdir");
    let root = temp.path();
    let dir = uv_dir(root);
    std::fs::create_dir_all(&dir).expect("create uv dir");
    std::fs::write(
        dir.join("pyproject.toml"),
        format!(
            "[project]\nname = \"sample-pkg-e2e\"\nversion = \"0.0.0\"\ndependencies = \
             [\"{UV_DEPENDENCY}{UV_STALE_SPEC}; python_version < '3.11'\"]\n"
        ),
    )
    .expect("write pyproject.toml");
    write_uv_lock_project_shape(root, UV_FRESH_SPEC);

    let findings = stale_uv_lock_findings(&uv_dir(root));

    assert!(
        findings.is_empty(),
        "a marker-conditional requirement is not directly comparable: {findings:?}"
    );
}

/// A name declared in `[tool.uv.sources]` has its resolution overridden (path, git, URL,
/// workspace, or an alternate index) -- exactly `render_pyproject`'s own `Local` dependency
/// mode, which writes the bare unconstrained name here and the real source in that table.
/// Comparing it against the lock's registry-shaped specifier text would be a false positive.
#[test]
fn stale_uv_lock_findings_ignores_a_source_overridden_dependency() {
    let temp = tempfile::tempdir().expect("tempdir");
    let root = temp.path();
    let dir = uv_dir(root);
    std::fs::create_dir_all(&dir).expect("create uv dir");
    std::fs::write(
        dir.join("pyproject.toml"),
        format!(
            "[project]\nname = \"sample-pkg-e2e\"\nversion = \"0.0.0\"\ndependencies = \
             [\"{UV_DEPENDENCY}\"]\n\n[tool.uv]\nsources.{UV_DEPENDENCY} = {{ path = \"../..\" }}\n"
        ),
    )
    .expect("write pyproject.toml");
    write_uv_lock_project_shape(root, UV_FRESH_SPEC);

    let findings = stale_uv_lock_findings(&uv_dir(root));

    assert!(
        findings.is_empty(),
        "a [tool.uv.sources]-overridden dependency is not directly comparable: {findings:?}"
    );
}

/// The run-level entry point: it must select `pyproject.toml` out of the generated path set,
/// and the error it returns must name the dependency, both specifiers, the lock, and the
/// remedy.
#[test]
fn check_generated_uv_lock_freshness_names_the_dependency_and_the_remedy() {
    let temp = tempfile::tempdir().expect("tempdir");
    let root = temp.path();
    let manifest = write_pyproject(root, UV_STALE_SPEC);
    write_uv_lock_project_shape(root, UV_FRESH_SPEC);
    let generated: HashSet<PathBuf> = [manifest].into_iter().collect();

    let error = check_generated_uv_lock_freshness(&generated).expect("a stale lock must fail the run");
    let message = format!("{error:#}");

    assert!(
        message.contains(UV_DEPENDENCY),
        "message must name the dependency: {message}"
    );
    assert!(
        message.contains(UV_STALE_SPEC),
        "message must name the pyproject.toml specifier: {message}"
    );
    assert!(
        message.contains(UV_FRESH_SPEC),
        "message must name the locked specifier: {message}"
    );
    assert!(message.contains("uv lock"), "message must name the remedy: {message}");
    assert!(
        message.contains(&uv_dir(root).join("uv.lock").display().to_string()),
        "message must name the lock: {message}"
    );
}

/// Control for the entry point, matching the pattern above: a lock whose specifier already
/// matches must return `None` so the run keeps its zero exit. This is the assertion that would
/// catch a regression turning this check into an unconditional failure.
#[test]
fn check_generated_uv_lock_freshness_passes_a_matching_lock() {
    let temp = tempfile::tempdir().expect("tempdir");
    let root = temp.path();
    let manifest = write_pyproject(root, UV_FRESH_SPEC);
    write_uv_lock_project_shape(root, UV_FRESH_SPEC);
    let generated: HashSet<PathBuf> = [manifest].into_iter().collect();

    assert!(
        check_generated_uv_lock_freshness(&generated).is_none(),
        "a matching lock must not fail the run"
    );
}

/// A generated path set containing no `pyproject.toml` at all must not walk anything.
#[test]
fn check_generated_uv_lock_freshness_ignores_non_manifest_paths() {
    let temp = tempfile::tempdir().expect("tempdir");
    let root = temp.path();
    write_pyproject(root, UV_STALE_SPEC);
    write_uv_lock_project_shape(root, UV_FRESH_SPEC);
    let generated: HashSet<PathBuf> = [uv_dir(root).join("tests/test_basic.py")].into_iter().collect();

    assert!(check_generated_uv_lock_freshness(&generated).is_none());
}

/// Coverage for [`check_generated_uv_lock_freshness_tolerating_pending_publish`]'s exemption
/// -- the uv sibling of the cargo `pending_publish` module, and the actual
/// real downstream incident this closes: `test_apps/python/pyproject.toml` requires
/// a downstream package `>=3.12.0` while PyPI still only has `3.11.6` published.
mod pending_publish {
    use super::*;
    use crate::core::config::ResolvedCrateConfig;
    use crate::core::config::e2e::{E2eConfig, PackageRef, RegistryConfig};

    /// A crate whose `[crates.e2e.registry.packages.python]` explicitly names `pkg_name` at
    /// `pkg_version` -- the only shape [`registry_self_dependency`] ever vouches for.
    fn resolved_cfg_with_python_registry_package(pkg_name: &str, pkg_version: &str) -> ResolvedCrateConfig {
        let e2e = E2eConfig {
            registry: RegistryConfig {
                packages: [(
                    "python".to_string(),
                    PackageRef {
                        name: Some(pkg_name.to_string()),
                        version: Some(pkg_version.to_string()),
                        ..PackageRef::default()
                    },
                )]
                .into_iter()
                .collect(),
                ..RegistryConfig::default()
            },
            ..E2eConfig::default()
        };
        ResolvedCrateConfig {
            e2e: Some(e2e),
            ..ResolvedCrateConfig::default()
        }
    }

    /// Control proving the exemption does real work: without it, this exact shape must still
    /// fail.
    #[test]
    fn plain_check_still_fails_on_a_pending_publish_disagreement() {
        let temp = tempfile::tempdir().expect("tempdir");
        let root = temp.path();
        let manifest = write_pyproject(root, UV_STALE_SPEC);
        write_uv_lock_project_shape(root, UV_FRESH_SPEC);
        let generated: HashSet<PathBuf> = [manifest].into_iter().collect();

        assert!(
            check_generated_uv_lock_freshness(&generated).is_some(),
            "control: the plain check has no pending-publish exemption and must still fail here"
        );
    }

    #[test]
    fn tolerating_variant_warns_instead_of_failing_when_the_requirement_matches_the_configured_registry_package() {
        let temp = tempfile::tempdir().expect("tempdir");
        let root = temp.path();
        let manifest = write_pyproject(root, UV_STALE_SPEC);
        write_uv_lock_project_shape(root, UV_FRESH_SPEC);
        let generated: HashSet<PathBuf> = [manifest].into_iter().collect();
        // `UV_STALE_SPEC` already carries a PEP 508 comparator (">="), so
        // `normalize_python_version` passes it through unchanged -- this is exactly what
        // alef's own e2e generator would have written for this registry package.
        let resolved_cfg = resolved_cfg_with_python_registry_package(UV_DEPENDENCY, UV_STALE_SPEC);

        let result = check_generated_uv_lock_freshness_tolerating_pending_publish(&generated, Some(&resolved_cfg));
        assert!(
            result.is_none(),
            "a disagreement fully explained by this crate's own configured registry \
             self-dependency must not fail the run: {result:?}"
        );
    }

    /// Without a resolved config, nothing can be classified as pending -- must behave exactly
    /// like the plain check.
    #[test]
    fn tolerating_variant_without_resolved_cfg_behaves_like_the_plain_check() {
        let temp = tempfile::tempdir().expect("tempdir");
        let root = temp.path();
        let manifest = write_pyproject(root, UV_STALE_SPEC);
        write_uv_lock_project_shape(root, UV_FRESH_SPEC);
        let generated: HashSet<PathBuf> = [manifest].into_iter().collect();

        assert!(
            check_generated_uv_lock_freshness_tolerating_pending_publish(&generated, None).is_some(),
            "no resolved config means no exemption is possible; this must still fail"
        );
    }

    /// The same crate shape as [`resolved_cfg_with_python_registry_package`], except the package
    /// declares no `version` -- it resolves from the workspace manifest instead.
    fn resolved_cfg_with_python_registry_package_without_version(pkg_name: &str) -> ResolvedCrateConfig {
        let e2e = E2eConfig {
            registry: RegistryConfig {
                packages: [(
                    "python".to_string(),
                    PackageRef {
                        name: Some(pkg_name.to_string()),
                        ..PackageRef::default()
                    },
                )]
                .into_iter()
                .collect(),
                ..RegistryConfig::default()
            },
            ..E2eConfig::default()
        };
        ResolvedCrateConfig {
            e2e: Some(e2e),
            ..ResolvedCrateConfig::default()
        }
    }

    /// The shape this exemption exists for in practice, and the one it used to miss: a consumer
    /// that declares `name`/`path` for its e2e package and lets the *version* resolve from the
    /// workspace manifest -- the documented way to avoid a hardpin that has to be bumped by hand
    /// every release. `registry_self_dependency` read `version` only from the explicit config
    /// field, while the generator that wrote the requirement being compared resolves it through
    /// `package.version` -> `resolved_version()`. The two disagreed about the same fact, so no
    /// exemption applied and `sync-versions` hard-failed the bump telling the operator to run
    /// `uv lock` against a version that, by construction, is not published yet. ~keep
    #[test]
    fn tolerating_variant_falls_back_to_the_resolved_workspace_version_when_the_package_pins_none() {
        let temp = tempfile::tempdir().expect("tempdir");
        let root = temp.path();
        let manifest = write_pyproject(root, "==0.17.0");
        write_uv_lock_project_shape(root, "==0.17.0rc11");
        let generated: HashSet<PathBuf> = [manifest].into_iter().collect();
        let version_from = root.join("Cargo.toml");
        std::fs::write(&version_from, "[package]\nname = \"sample\"\nversion = \"0.17.0\"\n")
            .expect("write workspace manifest");

        let resolved_cfg = ResolvedCrateConfig {
            version_from: version_from.display().to_string(),
            ..resolved_cfg_with_python_registry_package_without_version(UV_DEPENDENCY)
        };

        let result = check_generated_uv_lock_freshness_tolerating_pending_publish(&generated, Some(&resolved_cfg));
        assert!(
            result.is_none(),
            "a self-dependency whose version resolves from the workspace manifest is still this \
             crate's own pending release and must warn, not fail: {result:?}"
        );
    }

    /// Identity stays strict: the version fallback must not let a package with no configured
    /// name be vouched for. Without this, widening the version half would quietly widen the
    /// identity half too, which is the risk the original conservative reading was protecting.
    #[test]
    fn the_version_fallback_does_not_relax_the_identity_requirement() {
        let temp = tempfile::tempdir().expect("tempdir");
        let root = temp.path();
        let manifest = write_pyproject(root, "==0.17.0");
        write_uv_lock_project_shape(root, "==0.17.0rc11");
        let generated: HashSet<PathBuf> = [manifest].into_iter().collect();
        let version_from = root.join("Cargo.toml");
        std::fs::write(&version_from, "[package]\nname = \"sample\"\nversion = \"0.17.0\"\n")
            .expect("write workspace manifest");

        let e2e = E2eConfig {
            registry: RegistryConfig {
                packages: [("python".to_string(), PackageRef::default())].into_iter().collect(),
                ..RegistryConfig::default()
            },
            ..E2eConfig::default()
        };
        let resolved_cfg = ResolvedCrateConfig {
            e2e: Some(e2e),
            version_from: version_from.display().to_string(),
            ..ResolvedCrateConfig::default()
        };

        assert!(
            check_generated_uv_lock_freshness_tolerating_pending_publish(&generated, Some(&resolved_cfg)).is_some(),
            "no configured package name means no identity to vouch for, resolved version or not"
        );
    }

    /// The false-negative guard: a genuinely stale THIRD-PARTY pin has nothing to do with this
    /// crate's own registry self-dependency and must still fail even when a resolved config is
    /// supplied -- the exemption must not blanket-suppress every finding just because
    /// generation happens to know its own registry package identity.
    #[test]
    fn tolerating_variant_still_fails_on_a_disagreement_not_explained_by_pending_publish() {
        let temp = tempfile::tempdir().expect("tempdir");
        let root = temp.path();
        let manifest = write_pyproject(root, UV_STALE_SPEC);
        write_uv_lock_project_shape(root, UV_FRESH_SPEC);
        let generated: HashSet<PathBuf> = [manifest].into_iter().collect();
        // The configured registry package name/version do NOT match the finding's own
        // dependency/requirement at all -- an unrelated self-dependency identity, so nothing
        // here explains this drift.
        let resolved_cfg = resolved_cfg_with_python_registry_package("unrelated-package", ">=9.9.9");

        assert!(
            check_generated_uv_lock_freshness_tolerating_pending_publish(&generated, Some(&resolved_cfg)).is_some(),
            "a third-party lock drift unrelated to this crate's own registry self-dependency \
             must still fail the run"
        );
    }
}