5
5
import os
6
6
import subprocess
7
7
import typing as typ
8
+ from pathlib import Path
8
9
9
10
import pytest
10
11
from shared_actions_conftest import (
15
16
_register_rustup_toolchain_stub ,
16
17
)
17
18
19
+ from cmd_utils import RunResult
20
+
18
21
if typ .TYPE_CHECKING :
19
- from pathlib import Path
20
22
from types import ModuleType
21
23
22
24
from shared_actions_conftest import CmdMox
@@ -871,12 +873,12 @@ def timeout_runtime(_name: str, *, cwd: object | None = None) -> bool:
871
873
872
874
873
875
def test_configure_windows_linkers_prefers_toolchain_gcc (
874
- main_module : ModuleType ,
876
+ toolchain_module : ModuleType ,
875
877
module_harness : HarnessFactory ,
876
878
tmp_path : Path ,
877
879
) -> None :
878
880
"""Toolchain-provided GCC is preferred for Windows host builds."""
879
- harness = module_harness (main_module )
881
+ harness = module_harness (toolchain_module )
880
882
harness .patch_platform ("win32" )
881
883
toolchain_name = "1.89.0-x86_64-pc-windows-gnu"
882
884
host_triple = "x86_64-pc-windows-gnu"
@@ -895,23 +897,24 @@ def fake_run(
895
897
args : list [str ],
896
898
* ,
897
899
allowed_names : tuple [str , ...],
898
- capture_output : bool = False ,
899
- text : bool = False ,
900
- check : bool = False ,
901
- ** _ : object ,
902
- ) -> subprocess .CompletedProcess [str ]:
900
+ method : str = "run" ,
901
+ ** run_kwargs : object ,
902
+ ) -> RunResult :
903
903
_ = allowed_names
904
904
cmd = [executable , * args ]
905
- assert cmd [:2 ] == [rustup_path , "which" ]
906
- return subprocess .CompletedProcess (cmd , 0 , stdout = str (rustc_path ))
907
-
908
- harness .monkeypatch .setattr (main_module , "run_validated" , fake_run )
909
- harness .monkeypatch .setattr (main_module .shutil , "which" , lambda name : None )
905
+ assert Path (cmd [0 ]) == Path (rustup_path )
906
+ assert cmd [1 ] == "which"
907
+ assert method == "run"
908
+ assert not run_kwargs
909
+ return RunResult (0 , str (rustc_path ), "" )
910
+
911
+ harness .monkeypatch .setattr (toolchain_module , "run_validated" , fake_run )
912
+ harness .monkeypatch .setattr (toolchain_module .shutil , "which" , lambda name : None )
910
913
harness .monkeypatch .delenv (
911
914
"CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER" , raising = False
912
915
)
913
916
914
- main_module .configure_windows_linkers (toolchain_name , host_triple , rustup_path )
917
+ toolchain_module .configure_windows_linkers (toolchain_name , host_triple , rustup_path )
915
918
916
919
expected = str (host_linker )
917
920
assert os .environ ["CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER" ] == expected
@@ -925,17 +928,18 @@ def fake_run(
925
928
],
926
929
)
927
930
def test_configure_windows_linkers_sets_cross_linker (
928
- main_module : ModuleType ,
931
+ toolchain_module : ModuleType ,
929
932
module_harness : HarnessFactory ,
930
933
tmp_path : Path ,
931
934
linker_name : str ,
932
935
) -> None :
933
936
"""Cross linkers discovered on PATH are exported for non-host targets."""
934
- harness = module_harness (main_module )
937
+ harness = module_harness (toolchain_module )
935
938
harness .patch_platform ("win32" )
936
939
toolchain_name = "1.89.0-x86_64-pc-windows-gnu"
937
940
rustup_path = "/usr/bin/rustup"
938
941
host_triple = "x86_64-pc-windows-gnu"
942
+ target_triple = "aarch64-pc-windows-gnu"
939
943
940
944
toolchain_root = tmp_path / "toolchain"
941
945
rustc_path = toolchain_root / "bin" / "rustc.exe"
@@ -952,32 +956,195 @@ def fake_run(
952
956
args : list [str ],
953
957
* ,
954
958
allowed_names : tuple [str , ...],
955
- capture_output : bool = False ,
956
- text : bool = False ,
957
- check : bool = False ,
958
- ** _ : object ,
959
- ) -> subprocess .CompletedProcess [str ]:
959
+ method : str = "run" ,
960
+ ** run_kwargs : object ,
961
+ ) -> RunResult :
960
962
_ = allowed_names
961
963
cmd = [executable , * args ]
962
- assert cmd [:2 ] == [rustup_path , "which" ]
963
- return subprocess .CompletedProcess (cmd , 0 , stdout = str (rustc_path ))
964
+ assert Path (cmd [0 ]) == Path (rustup_path )
965
+ assert cmd [1 ] == "which"
966
+ assert method == "run"
967
+ assert not run_kwargs
968
+ return RunResult (0 , str (rustc_path ), "" )
964
969
965
- harness .monkeypatch .setattr (main_module , "run_validated" , fake_run )
970
+ harness .monkeypatch .setattr (toolchain_module , "run_validated" , fake_run )
966
971
967
972
def fake_which (name : str ) -> str | None :
968
973
return str (cross_linker ) if name == linker_name else None
969
974
970
- harness .monkeypatch .setattr (main_module .shutil , "which" , fake_which )
975
+ harness .monkeypatch .setattr (toolchain_module .shutil , "which" , fake_which )
971
976
harness .monkeypatch .delenv (
972
977
"CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER" , raising = False
973
978
)
974
979
harness .monkeypatch .delenv (
975
980
"CARGO_TARGET_AARCH64_PC_WINDOWS_GNU_LINKER" , raising = False
976
981
)
977
982
978
- main_module .configure_windows_linkers (toolchain_name , host_triple , rustup_path )
983
+ toolchain_module .configure_windows_linkers (
984
+ toolchain_name , target_triple , rustup_path
985
+ )
979
986
980
987
host_env = os .environ ["CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER" ]
981
988
cross_env = os .environ ["CARGO_TARGET_AARCH64_PC_WINDOWS_GNU_LINKER" ]
982
989
assert host_env == str (host_linker )
983
990
assert cross_env == str (cross_linker )
991
+
992
+
993
+ def test_configure_windows_linkers_raises_on_rustup_failure (
994
+ toolchain_module : ModuleType ,
995
+ module_harness : HarnessFactory ,
996
+ ) -> None :
997
+ """Rustup discovery failures propagate as CalledProcessError."""
998
+ harness = module_harness (toolchain_module )
999
+ harness .patch_platform ("win32" )
1000
+ toolchain_name = "1.89.0-x86_64-pc-windows-gnu"
1001
+ host_triple = "x86_64-pc-windows-gnu"
1002
+ rustup_path = "/usr/bin/rustup"
1003
+
1004
+ def fake_run (
1005
+ executable : str ,
1006
+ args : list [str ],
1007
+ * ,
1008
+ allowed_names : tuple [str , ...],
1009
+ method : str = "run" ,
1010
+ ** run_kwargs : object ,
1011
+ ) -> RunResult :
1012
+ _ = allowed_names
1013
+ assert method == "run"
1014
+ assert not run_kwargs
1015
+ assert Path (executable ) == Path (rustup_path )
1016
+ assert args
1017
+ assert args [0 ] == "which"
1018
+ return RunResult (9 , "" , "rustup error" )
1019
+
1020
+ harness .monkeypatch .setattr (toolchain_module , "run_validated" , fake_run )
1021
+ harness .monkeypatch .setattr (toolchain_module .shutil , "which" , lambda name : None )
1022
+ harness .monkeypatch .delenv (
1023
+ "CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER" , raising = False
1024
+ )
1025
+
1026
+ with pytest .raises (subprocess .CalledProcessError ) as excinfo :
1027
+ toolchain_module .configure_windows_linkers (
1028
+ toolchain_name , host_triple , rustup_path
1029
+ )
1030
+
1031
+ exc = excinfo .value
1032
+ assert exc .returncode == 9
1033
+ assert Path (exc .cmd [0 ]) == Path (rustup_path )
1034
+ assert exc .cmd [1 ] == "which"
1035
+ assert exc .stderr == "rustup error"
1036
+ assert "CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER" not in os .environ
1037
+
1038
+
1039
+ def test_configure_windows_linkers_ignores_missing_rustup (
1040
+ toolchain_module : ModuleType ,
1041
+ module_harness : HarnessFactory ,
1042
+ ) -> None :
1043
+ """Missing rustup executables do not raise during linker configuration."""
1044
+ harness = module_harness (toolchain_module )
1045
+ harness .patch_platform ("win32" )
1046
+ toolchain_name = "1.89.0-x86_64-pc-windows-gnu"
1047
+ host_triple = "x86_64-pc-windows-gnu"
1048
+ rustup_path = "/usr/bin/rustup"
1049
+
1050
+ def fake_run (
1051
+ executable : str ,
1052
+ args : list [str ],
1053
+ * ,
1054
+ allowed_names : tuple [str , ...],
1055
+ method : str = "run" ,
1056
+ ** run_kwargs : object ,
1057
+ ) -> RunResult :
1058
+ _ = allowed_names
1059
+ assert method == "run"
1060
+ assert not run_kwargs
1061
+ assert Path (executable ) == Path (rustup_path )
1062
+ assert args [0 ] == "which"
1063
+ message = "rustup not found"
1064
+ raise FileNotFoundError (message )
1065
+
1066
+ harness .monkeypatch .setattr (toolchain_module , "run_validated" , fake_run )
1067
+ harness .monkeypatch .setattr (toolchain_module .shutil , "which" , lambda _ : None )
1068
+ harness .monkeypatch .delenv (
1069
+ "CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER" , raising = False
1070
+ )
1071
+
1072
+ toolchain_module .configure_windows_linkers (toolchain_name , host_triple , rustup_path )
1073
+
1074
+ assert "CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER" not in os .environ
1075
+
1076
+
1077
+ @pytest .fixture
1078
+ def ensure_packaging_version () -> None :
1079
+ """Ensure packaging.version is importable for dependency guards."""
1080
+ import packaging .version # noqa: F401
1081
+
1082
+
1083
+ def test_main_propagates_windows_linker_rustup_failure (
1084
+ ensure_packaging_version : None ,
1085
+ main_module : ModuleType ,
1086
+ toolchain_module : ModuleType ,
1087
+ module_harness : HarnessFactory ,
1088
+ ) -> None :
1089
+ """Behavioural coverage for rustup discovery failures in the action entrypoint."""
1090
+ harness = module_harness (main_module )
1091
+ harness .patch_platform ("win32" )
1092
+ toolchain_name = "1.89.0-x86_64-pc-windows-gnu"
1093
+ rustup_path = "/usr/bin/rustup"
1094
+ target = "x86_64-pc-windows-gnu"
1095
+
1096
+ def fake_run (
1097
+ executable : str ,
1098
+ args : list [str ],
1099
+ * ,
1100
+ allowed_names : tuple [str , ...],
1101
+ method : str = "run" ,
1102
+ ** run_kwargs : object ,
1103
+ ) -> RunResult :
1104
+ _ = allowed_names
1105
+ assert method == "run"
1106
+ assert not run_kwargs
1107
+ assert Path (executable ) == Path (rustup_path )
1108
+ assert args [0 ] == "which"
1109
+ return RunResult (9 , "" , "rustup error" )
1110
+
1111
+ harness .monkeypatch .setattr (toolchain_module , "run_validated" , fake_run )
1112
+ harness .patch_attr (
1113
+ "configure_windows_linkers" , toolchain_module .configure_windows_linkers
1114
+ )
1115
+ harness .patch_attr ("_resolve_target_argument" , lambda value : value )
1116
+ harness .patch_attr ("_ensure_rustup_exec" , lambda : rustup_path )
1117
+ harness .patch_attr (
1118
+ "_resolve_toolchain" ,
1119
+ lambda * _ : (toolchain_name , [toolchain_name ]),
1120
+ )
1121
+ harness .patch_attr ("_ensure_target_installed" , lambda * _ : True )
1122
+ harness .patch_attr (
1123
+ "_decide_cross_usage" ,
1124
+ lambda * _args , ** _kwargs : main_module ._CrossDecision (
1125
+ cross_path = None ,
1126
+ cross_version = None ,
1127
+ use_cross = False ,
1128
+ cross_toolchain_spec = None ,
1129
+ cargo_toolchain_spec = None ,
1130
+ use_cross_local_backend = False ,
1131
+ docker_present = False ,
1132
+ podman_present = False ,
1133
+ has_container = False ,
1134
+ container_engine = None ,
1135
+ requires_cross_container = False ,
1136
+ ),
1137
+ )
1138
+ harness .monkeypatch .delenv (
1139
+ "CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER" , raising = False
1140
+ )
1141
+
1142
+ with pytest .raises (subprocess .CalledProcessError ) as excinfo :
1143
+ main_module .main (target , toolchain_name )
1144
+
1145
+ exc = excinfo .value
1146
+ assert exc .returncode == 9
1147
+ assert Path (exc .cmd [0 ]) == Path (rustup_path )
1148
+ assert exc .cmd [1 ] == "which"
1149
+ assert exc .stderr == "rustup error"
1150
+ assert "CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER" not in os .environ
0 commit comments