diff --git a/src/core/IronPython.Modules/nt.cs b/src/core/IronPython.Modules/nt.cs index dbc80ff0d..239d0c313 100644 --- a/src/core/IronPython.Modules/nt.cs +++ b/src/core/IronPython.Modules/nt.cs @@ -1144,7 +1144,7 @@ private static Process MakeProcess() { private static object SpawnProcessImpl(CodeContext/*!*/ context, Process process, int mode, string path, object? args, [CallerMemberName] string? methodname = null) { try { - process.StartInfo.Arguments = ArgumentsToString(context, args, methodname); + SetArguments(process.StartInfo, ArgumentsToList(context, args, methodname)); process.StartInfo.FileName = path; process.StartInfo.UseShellExecute = false; } catch (Exception e) { @@ -1179,6 +1179,48 @@ private static object SpawnProcessImpl(CodeContext/*!*/ context, Process process _processToIdMapping[id] = process; return ScriptingRuntimeHelpers.Int32ToObject(id); } + + // Set the process arguments from the list of args + static void SetArguments(ProcessStartInfo startInfo, List args) { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { + startInfo.Arguments = string.Join(" ", args); + return; + } + +#if NET + foreach (var arg in args) { + startInfo.ArgumentList.Add(arg); + } +#else + startInfo.Arguments = string.Join(" ", args.Select(QuoteIfNeeded)); + + static string QuoteIfNeeded(string str) { + if (str.Contains(' ')) { + return "\"" + str.Replace("\"", "\"\"") + "\""; + } + return str; + } +#endif + } + + static List ArgumentsToList(CodeContext/*!*/ context, object? args, string? methodname) { + IEnumerator? argsEnumerator; + if (!PythonOps.TryGetEnumerator(context, args, out argsEnumerator)) { + throw PythonOps.TypeErrorForBadInstance("args parameter must be sequence, not {0}", args); + } + + var list = new List(); + try { + // skip the first element, which is the name of the command being run + argsEnumerator.MoveNext(); + while (argsEnumerator.MoveNext()) { + list.Add(ConvertToFsString(context, argsEnumerator.Current, "elements of 'args'", methodname)); + } + } finally { + (argsEnumerator as IDisposable)?.Dispose(); + } + return list; + } } /// @@ -1201,45 +1243,6 @@ private static void SetEnvironment(CodeContext context, System.Collections.Speci } #endif - /// - /// Convert a sequence of args to a string suitable for using to spawn a process. - /// - private static string ArgumentsToString(CodeContext/*!*/ context, object? args, string? methodname) { - IEnumerator? argsEnumerator; - StringBuilder? sb = null; - if (!PythonOps.TryGetEnumerator(context, args, out argsEnumerator)) { - throw PythonOps.TypeErrorForBadInstance("args parameter must be sequence, not {0}", args); - } - - bool space = false; - try { - // skip the first element, which is the name of the command being run - argsEnumerator.MoveNext(); - while (argsEnumerator.MoveNext()) { - if (sb == null) sb = new StringBuilder(); // lazy creation - string strarg = ConvertToFsString(context, argsEnumerator.Current, "elements of 'args'", methodname); - if (space) { - sb.Append(' '); - } - if (strarg.Contains(' ')) { - sb.Append('"'); - // double quote any existing quotes - sb.Append(strarg.Replace("\"", "\"\"")); - sb.Append('"'); - } else { - sb.Append(strarg); - } - space = true; - } - } finally { - IDisposable? disposable = argsEnumerator as IDisposable; - if (disposable != null) disposable.Dispose(); - } - - if (sb == null) return ""; - return sb.ToString(); - } - #if FEATURE_PROCESS [SupportedOSPlatform("windows"), PythonHidden(PlatformsAttribute.PlatformFamily.Unix)] public static void startfile([NotNone] string filepath, string operation = "open") { diff --git a/tests/suite/modules/system_related/test_nt.py b/tests/suite/modules/system_related/test_nt.py index 95d02e284..bed9137c2 100644 --- a/tests/suite/modules/system_related/test_nt.py +++ b/tests/suite/modules/system_related/test_nt.py @@ -902,7 +902,6 @@ def test__getfullpathname_neg(self): for bad in [None, 0, 34, -big(12345), 3.14, object, self.test__getfullpathname]: self.assertRaises(TypeError, nt._getfullpathname, bad) - @unittest.skipIf(is_netcoreapp, 'TODO: figure out') @unittest.skipUnless(sys.platform == "win32", 'windir is Windows specific') def test_cp15514(self): cmd_variation_list = ['%s -c "print(__name__)"' % sys.executable, diff --git a/tests/suite/test_stdconsole.py b/tests/suite/test_stdconsole.py index a40e9c6be..51205e701 100644 --- a/tests/suite/test_stdconsole.py +++ b/tests/suite/test_stdconsole.py @@ -68,8 +68,8 @@ def setUp(self): # "lastline" : valuestring is compared against the last line of the output # "regexp" : valuestring is a regular expression compared against the entire output def TestCommandLine(self, args, expected_output, expected_exitcode = 0): - if not is_cli: - # https://github.com/IronLanguages/ironpython3/issues/648 + if os.name == 'nt': + # must self-quote arguments with spawnv on Windows try: idx = args.index("-c") if idx + 1 < len(args):