Skip to content

Commit 17aef1b

Browse files
test(test-mode): add tests for prebuilt fallback path in bootstrap()
Co-Authored-By: Claude <claude@anthropic.com> Signed-off-by: Lalatendu Mohanty <lmohanty@redhat.com>
1 parent 0e53faf commit 17aef1b

File tree

1 file changed

+188
-0
lines changed

1 file changed

+188
-0
lines changed

tests/test_bootstrap_test_mode.py

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,3 +290,191 @@ def test_resolution_failure_raises_in_normal_mode(
290290
):
291291
with pytest.raises(RuntimeError, match="Version resolution failed"):
292292
bt.bootstrap(req=req, req_type=RequirementType.TOP_LEVEL)
293+
294+
295+
class TestPrebuiltFallback:
296+
"""Test prebuilt fallback behavior in test mode when build fails."""
297+
298+
def test_fallback_succeeds_no_failure_recorded(
299+
self, tmp_context: context.WorkContext, caplog: pytest.LogCaptureFixture
300+
) -> None:
301+
"""Test that successful fallback to prebuilt doesn't record a failure."""
302+
from packaging.version import Version
303+
304+
bt = bootstrapper.Bootstrapper(ctx=tmp_context, test_mode=True)
305+
req = Requirement("test-package>=1.0")
306+
307+
with (
308+
mock.patch.object(
309+
bt,
310+
"resolve_version",
311+
return_value=("https://sdist.url", Version("1.0")),
312+
),
313+
mock.patch.object(bt, "_add_to_graph"),
314+
mock.patch.object(bt, "_has_been_seen", return_value=False),
315+
mock.patch.object(bt, "_mark_as_seen"),
316+
# First call fails (build), second succeeds (fallback with force_prebuilt)
317+
mock.patch.object(
318+
bt,
319+
"_bootstrap_impl",
320+
side_effect=[RuntimeError("Build failed"), None],
321+
),
322+
mock.patch.object(
323+
bt,
324+
"_resolve_prebuilt_with_history",
325+
return_value=("https://wheel.url", Version("1.0")),
326+
),
327+
):
328+
bt.bootstrap(req=req, req_type=RequirementType.TOP_LEVEL)
329+
330+
# No failure recorded because fallback succeeded
331+
assert len(bt.failed_packages) == 0
332+
assert "successfully used pre-built wheel" in caplog.text
333+
334+
def test_fallback_version_mismatch_logs_warning(
335+
self, tmp_context: context.WorkContext, caplog: pytest.LogCaptureFixture
336+
) -> None:
337+
"""Test that version mismatch during fallback logs a warning."""
338+
from packaging.version import Version
339+
340+
bt = bootstrapper.Bootstrapper(ctx=tmp_context, test_mode=True)
341+
req = Requirement("test-package>=1.0")
342+
343+
with (
344+
mock.patch.object(
345+
bt,
346+
"resolve_version",
347+
return_value=("https://sdist.url", Version("1.0")),
348+
),
349+
mock.patch.object(bt, "_add_to_graph"),
350+
mock.patch.object(bt, "_has_been_seen", return_value=False),
351+
mock.patch.object(bt, "_mark_as_seen"),
352+
mock.patch.object(
353+
bt,
354+
"_bootstrap_impl",
355+
side_effect=[RuntimeError("Build failed"), None],
356+
),
357+
# Fallback resolves to different version
358+
mock.patch.object(
359+
bt,
360+
"_resolve_prebuilt_with_history",
361+
return_value=("https://wheel.url", Version("1.1")),
362+
),
363+
):
364+
bt.bootstrap(req=req, req_type=RequirementType.TOP_LEVEL)
365+
366+
# No failure recorded because fallback succeeded
367+
assert len(bt.failed_packages) == 0
368+
assert "version mismatch" in caplog.text
369+
assert "requested 1.0" in caplog.text
370+
assert "fallback 1.1" in caplog.text
371+
372+
def test_fallback_also_fails_records_original_error(
373+
self, tmp_context: context.WorkContext, caplog: pytest.LogCaptureFixture
374+
) -> None:
375+
"""Test that when fallback also fails, original build error is recorded."""
376+
from packaging.version import Version
377+
378+
bt = bootstrapper.Bootstrapper(ctx=tmp_context, test_mode=True)
379+
req = Requirement("test-package>=1.0")
380+
381+
with (
382+
mock.patch.object(
383+
bt,
384+
"resolve_version",
385+
return_value=("https://sdist.url", Version("1.0")),
386+
),
387+
mock.patch.object(bt, "_add_to_graph"),
388+
mock.patch.object(bt, "_has_been_seen", return_value=False),
389+
mock.patch.object(bt, "_mark_as_seen"),
390+
# Both build and fallback fail
391+
mock.patch.object(
392+
bt,
393+
"_bootstrap_impl",
394+
side_effect=[
395+
RuntimeError("Original build failed"),
396+
RuntimeError("Fallback also failed"),
397+
],
398+
),
399+
mock.patch.object(
400+
bt,
401+
"_resolve_prebuilt_with_history",
402+
return_value=("https://wheel.url", Version("1.0")),
403+
),
404+
):
405+
bt.bootstrap(req=req, req_type=RequirementType.TOP_LEVEL)
406+
407+
# Failure recorded with ORIGINAL error, not fallback error
408+
assert len(bt.failed_packages) == 1
409+
failure = bt.failed_packages[0]
410+
assert failure["package"] == "test-package"
411+
assert failure["version"] == "1.0"
412+
assert failure["failure_type"] == "bootstrap"
413+
assert "Original build failed" in failure["exception_message"]
414+
assert "pre-built fallback also failed" in caplog.text
415+
416+
def test_fallback_resolution_fails_records_original_error(
417+
self, tmp_context: context.WorkContext
418+
) -> None:
419+
"""Test that when prebuilt resolution fails, original build error is recorded."""
420+
from packaging.version import Version
421+
422+
bt = bootstrapper.Bootstrapper(ctx=tmp_context, test_mode=True)
423+
req = Requirement("test-package>=1.0")
424+
425+
with (
426+
mock.patch.object(
427+
bt,
428+
"resolve_version",
429+
return_value=("https://sdist.url", Version("1.0")),
430+
),
431+
mock.patch.object(bt, "_add_to_graph"),
432+
mock.patch.object(bt, "_has_been_seen", return_value=False),
433+
mock.patch.object(bt, "_mark_as_seen"),
434+
mock.patch.object(
435+
bt,
436+
"_bootstrap_impl",
437+
side_effect=RuntimeError("Original build failed"),
438+
),
439+
# Prebuilt resolution fails
440+
mock.patch.object(
441+
bt,
442+
"_resolve_prebuilt_with_history",
443+
side_effect=RuntimeError("No prebuilt available"),
444+
),
445+
):
446+
bt.bootstrap(req=req, req_type=RequirementType.TOP_LEVEL)
447+
448+
# Failure recorded with ORIGINAL error
449+
assert len(bt.failed_packages) == 1
450+
assert "Original build failed" in bt.failed_packages[0]["exception_message"]
451+
452+
def test_build_failure_raises_in_normal_mode(
453+
self, tmp_context: context.WorkContext
454+
) -> None:
455+
"""Test that build failures raise immediately in normal mode (no fallback)."""
456+
from packaging.version import Version
457+
458+
bt = bootstrapper.Bootstrapper(ctx=tmp_context, test_mode=False)
459+
req = Requirement("test-package>=1.0")
460+
461+
with (
462+
mock.patch.object(
463+
bt,
464+
"resolve_version",
465+
return_value=("https://sdist.url", Version("1.0")),
466+
),
467+
mock.patch.object(bt, "_add_to_graph"),
468+
mock.patch.object(bt, "_has_been_seen", return_value=False),
469+
mock.patch.object(bt, "_mark_as_seen"),
470+
mock.patch.object(
471+
bt,
472+
"_bootstrap_impl",
473+
side_effect=RuntimeError("Build failed"),
474+
),
475+
):
476+
with pytest.raises(RuntimeError, match="Build failed"):
477+
bt.bootstrap(req=req, req_type=RequirementType.TOP_LEVEL)
478+
479+
# No fallback attempted in normal mode
480+
assert len(bt.failed_packages) == 0

0 commit comments

Comments
 (0)