Skip to content

Add test environment cloning optimization for faster CI - #15677

Closed
jezdez wants to merge 4 commits into
mainfrom
ci/test-env-cloning-optimization
Closed

Add test environment cloning optimization for faster CI#15677
jezdez wants to merge 4 commits into
mainfrom
ci/test-env-cloning-optimization

Conversation

@jezdez

@jezdez jezdez commented Feb 4, 2026

Copy link
Copy Markdown
Member

Summary

This PR adds test environment cloning optimization to speed up CI tests that create Python environments. Instead of running conda create for each test (~10s+), eligible tests can now clone from a session-scoped template environment (~2s).

Analysis of Current Test Environment Creation

TOTAL tmp_env() CALLS: ~223

╔══════════════════════════════════════════════════════════════════════════════╗
║                    DISTRIBUTION BY CATEGORY                                  ║
╠══════════════════════════════════════════════════════════════════════════════╣
║ CATEGORY                          │ COUNT │ % OF TOTAL │ CLONEABLE?         ║
╠═══════════════════════════════════╪═══════╪════════════╪════════════════════╣
║ Empty/Shallow (no packages)       │   71  │    32%     │ N/A (already fast) ║
║ "python" (generic, no version)    │    6  │     3%     │ YES ✓              ║
║ "python=X.Y" matching CI matrix   │   ~6  │     3%     │ YES ✓ (when match) ║
║ "python=X.Y", "pip"               │    4  │     2%     │ YES ✓ (when match) ║
║ Python with non-matching version  │  ~30  │    13%     │ No                 ║
║ Test packages (test channel)      │  ~44  │    20%     │ No                 ║
║ Variable/Dynamic (pkg, path, etc) │  ~30  │    13%     │ Depends            ║
║ Clone operations (--clone)        │    4  │     2%     │ N/A                ║
║ Other (special packages/flags)    │  ~28  │    12%     │ No                 ║
╚═══════════════════════════════════╧═══════╧════════════╧════════════════════╝

Key Findings

  1. 71 calls (32%) create EMPTY environments - already fast, no optimization needed
  2. ~16 calls (7%) can benefit from cloning when CI Python version matches spec
  3. Version-aware matching allows python=3.10 specs to clone when CI runs with 3.10

Optimization Strategy

This PR implements version-aware template cloning:

  1. Single template environment created at session start with python + pip
  2. Version-aware matching: specs like python=3.10 match if template has Python 3.10.x
  3. Fall back to conda create when version constraints don't match or packages missing

How Version Matching Works

# Template has Python 3.10.4, pip 23.0

"python"matches (no version constraint)
"python=3.10"matches (template has 3.10.x)
"python>=3.9"matches (template has >= 3.9)
"python=3.11"NO match (template has 3.10, not 3.11)
"python", "pip"matches (both present)
"python", "numpy"NO match (numpy not in template)

Expected Impact

CI Python Version Tests Benefiting Time Saved
3.10 ~16 tests ~128s
3.14 ~6 tests ~48s

The impact varies by CI matrix:

  • When CI runs with Python 3.10: tests requesting python=3.10 clone ✓
  • When CI runs with Python 3.14: only generic python specs clone
Metric Conservative (before) Version-aware (now)
Tests cloning (3.10 CI) 6 (~3%) ~16 (~7%)
Tests cloning (3.14 CI) 6 (~3%) ~6 (~3%)
Time saved per clone ~8s ~8s
Setup overhead ~10s ~10s

Implementation Details

TemplateEnvManager

  • Stores package_versions dict (name → version) from template
  • can_satisfy(specs) checks:
    1. All required packages present
    2. Version constraints satisfied by template's versions
  • Uses conda's VersionSpec for proper version matching

template_env_manager fixture

  • Session-scoped fixture creating ONE template at startup
  • Template contains: python + pip (versions from CI matrix)

tmp_env fixture behavior

  1. Check if template_manager.can_satisfy(args) returns True
  2. If yes: conda create --clone {template} (~2s)
  3. If no: conda create {args} (~10s+)

References

Based on optimizations from:

Test Plan

  • Run CI to verify the optimization works across platforms
  • Add version-aware matching tests
  • Compare CI run times before/after
  • Verify no test regressions

@conda-bot conda-bot added the cla-signed [bot] added once the contributor has signed the CLA label Feb 4, 2026
@jezdez
jezdez force-pushed the ci/test-env-cloning-optimization branch from f05403b to 333f1ef Compare February 4, 2026 14:15
@github-project-automation github-project-automation Bot moved this to 🆕 New in 🔎 Review Feb 4, 2026
@jezdez
jezdez force-pushed the ci/test-env-cloning-optimization branch 3 times, most recently from 8e2e64e to b8930ef Compare February 4, 2026 14:27
@codspeed-hq

codspeed-hq Bot commented Feb 4, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 96.67%

❌ 2 regressed benchmarks
✅ 21 untouched benchmarks
⏩ 21 skipped benchmarks1

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation test_list[classic] 223.1 ms 6,705.4 ms -96.67%
Simulation test_list[libmamba] 3.4 s 6.7 s -49.24%

Comparing ci/test-env-cloning-optimization (41f6f02) with main (059ca0a)

Open in CodSpeed

Footnotes

  1. 21 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@jezdez
jezdez force-pushed the ci/test-env-cloning-optimization branch from b8930ef to 1db1f08 Compare February 4, 2026 15:46
Add TemplateEnvManager that creates and caches template environments
on demand for different Python versions and package combinations.

Key features:
- Multiple Python version templates (3.10, 3.11, 3.12, 3.13) created on demand
- Python + pip templates for common test scenarios
- "Clone + install delta" strategy: clone Python template, install extras
- Platform-specific fast copy methods:
  - macOS (APFS): cp -c for copy-on-write (~1.8s vs ~10s for conda create)
  - Linux: cp --reflink=auto for reflinks on btrfs/xfs
  - Windows: robocopy for multi-threaded copy, falls back to shutil.copytree

Analysis of test suite showed:
- 76 calls (34%) create empty environments - already fast
- 23 calls (10%) create Python environments - can benefit from cloning
- Many tests use specific Python versions (3.10, 3.11, 3.12, 3.13)

The tmp_env fixture now automatically uses the template manager when
creating Python environments, significantly reducing test setup time.

Based on optimizations from conda-build PR #5904 and conda-pypi PR #236.
@jezdez
jezdez force-pushed the ci/test-env-cloning-optimization branch from 1db1f08 to b944a12 Compare February 4, 2026 18:34
Replace complex multi-version template management with a simpler approach:
- Create ONE template (python + pip) at session start
- Use can_satisfy() to check if template has all required packages by name
- Clone if satisfied, fall back to conda create otherwise

This removes ~200 lines of complexity (parse_python_spec, find_matching_template,
delta package installation) while still benefiting the majority of tests that
only need "python" or "python" + "pip".
Specs like "python=3.10" were incorrectly matching the template
because we only checked package names. This caused tests to get
cloned environments with the wrong Python version.

Now can_satisfy() rejects specs with ANY version constraint,
ensuring we only clone when the template definitely satisfies
the test requirements.
can_satisfy() now matches specs WITH version constraints if the
template's installed version satisfies them:
- "python=3.10" matches if template has Python 3.10.x
- "python>=3.9" matches if template has Python >= 3.9

This significantly increases the number of tests that can benefit
from cloning when CI runs with a specific Python version.

For example, when CI matrix runs with Python 3.10:
- "python" specs: 6 tests (clone)
- "python=3.10" specs: ~6 tests (clone)
- "python=3.10", "pip" specs: 4 tests (clone)
Total: ~16 tests benefit vs only 6 with conservative approach
@jezdez

jezdez commented Feb 4, 2026

Copy link
Copy Markdown
Member Author

Closing this PR

After thorough analysis, the optimization provides minimal benefit for the complexity added.

Analysis Results

Metric Value
Tests benefiting from cloning ~6 out of 223 (~3%)
Time saved per CI run ~38s net (after template overhead)
Code complexity added ~150 lines

Why It Doesn't Help More

  1. 71 tests (32%) already use empty tmp_env() - instant, no optimization needed
  2. ~100 tests need specific packages not in any template (test channel packages like small-executable, dependent)
  3. ~40 tests need specific Python versions (python=3.10, python=3.11) that won't match template
  4. Only 6 tests use generic "python" without version constraints

Root Cause

The conda test suite wasn't designed with environment reuse in mind. Tests request diverse, specific requirements that a single (or even multiple) template(s) can't efficiently satisfy.

The conda-build implementation (#5904) worked because their test patterns were different - more tests used generic Python specs.

Better Alternatives to Explore

  1. Increase test parallelization - split test groups more granularly
  2. Better use of session-scoped fixtures - session_tmp_env exists but is underutilized
  3. Refactor tests to use shallow=True - some tests create full environments but only need metadata
  4. Ensure package caching is optimal - verify CI caches extracted packages

I'll explore these alternatives in a separate effort.

@jezdez jezdez closed this Feb 4, 2026
@github-project-automation github-project-automation Bot moved this from 🆕 New to 🏁 Done in 🔎 Review Feb 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed [bot] added once the contributor has signed the CLA

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

2 participants