public IEnumerator TestUpdatePackagesWithOneUpgrade()
        {
            using (Py.GIL())
            {
                dynamic shlex = PythonEngine.ImportModule("shlex");

                using (var env = new PyTestVenv())
                {
#if UNITY_EDITOR_WIN
                    string pythonVenvInterpreter = Path.Combine(env.path, "Scripts", "python.exe");
                    // CallPipFreeze, PYTHONPATH limited to py venv path
                    string pythonPath = Path.Combine(env.path, "Lib", "site-packages");
#else
                    string pythonVenvInterpreter = Path.Combine(env.path, "bin", "python3");
                    string pythonPath            = Path.Combine(env.path, "lib", "site-packages", "python3.7", "site-packages");
#endif
                    // Install toml 0.9.0 into the py venv
                    var     argsStr = new PyString("-m pip install toml==0.9.0");
                    var     args    = shlex.split(argsStr);
                    dynamic proc    = spawn(pythonVenvInterpreter, args,
                                            wantLogging: false);
                    yield return(WaitForProcessEnd(proc, 20));

                    // Need to update pip, piptools requires pip._internal.commands.create_command, which is not available witout updating
                    argsStr = new PyString(" -m pip install pip -U");
                    args    = shlex.split(argsStr);
                    dynamic envOverride = new PyDict();
                    envOverride["PYTHONPATH"] = new PyString(pythonPath);
                    dynamic proc2 = spawn(pythonVenvInterpreter, args,
                                          env_override: envOverride,
                                          wantLogging: false);
                    yield return(WaitForProcessEnd(proc2, 20));

                    // Call UpdatePackages with a requirements.txt file containing only a requirement to toml 0.10.0
                    var testRequirementFile = Path.Combine(TestsPath, "test_requirements_1.txt");
                    PipPackages.UpdatePackages(testRequirementFile, pythonVenvInterpreter);

                    // NOTE:
                    // in this case, this not pip-tools diff/sync funcs that remove toml 9 for toml 10,
                    // it is pip that sees another version of the same package exists and then uninstall
                    // the current version before installing the required one

                    // Check that the only package in the py venv is toml 0.10.0
                    var output = CallPipFreeze(pythonVenvInterpreter, pythonPath);
                    Assert.That(output, Is.EqualTo("toml==0.10.0\n"));
                }
            }
        }
        public IEnumerator TestUpdatePackagesWithOneDowngrade()
        {
            using (Py.GIL())
            {
                dynamic shlex = PythonEngine.ImportModule("shlex");

                using (var env = new PyTestVenv())
                {
#if UNITY_EDITOR_WIN
                    string pythonVenvInterpreter = Path.Combine(env.path, "Scripts", "python.exe");
                    // CallPipFreeze, PYTHONPATH limited to py venv path
                    string pythonPath = Path.Combine(env.path, "Lib", "site-packages");
#else
                    string pythonVenvInterpreter = Path.Combine(env.path, "bin", "python3");
                    string pythonPath            = Path.Combine(env.path, "lib", "site-packages", "python3.7", "site-packages");
#endif
                    // Install toml 0.10.0 into the py venv
                    var     argsStr = new PyString("-m pip install toml==0.10.0");
                    var     args    = shlex.split(argsStr);
                    dynamic proc    = spawn(pythonVenvInterpreter, args,
                                            wantLogging: false);
                    yield return(WaitForProcessEnd(proc, 20));

                    argsStr = new PyString(" -m pip install pip -U");
                    args    = shlex.split(argsStr);
                    dynamic envOverride = new PyDict();
                    envOverride["PYTHONPATH"] = new PyString(pythonPath);
                    dynamic proc2 = spawn(pythonVenvInterpreter, args,
                                          env_override: envOverride,
                                          wantLogging: false);
                    yield return(WaitForProcessEnd(proc2, 20));

                    // Call UpdatePackages with a requirements.txt file containing only a requirement to toml 0.9.0
                    var testRequirementFile = Path.Combine(TestsPath, "test_requirements_2.txt");
                    PipPackages.UpdatePackages(testRequirementFile, pythonVenvInterpreter);

                    // Check that the only package in the py venv is toml 0.9.0

                    var output = CallPipFreeze(pythonVenvInterpreter, pythonPath);
                    Assert.That(output, Is.EqualTo("toml==0.9.0\n"));
                }
            }
        }
        public IEnumerator TestUpdatePackagesWithSeveralPackages()
        {
            using (Py.GIL())
            {
                dynamic shlex = PythonEngine.ImportModule("shlex");

                using (var env = new PyTestVenv())
                {
#if UNITY_EDITOR_WIN
                    string pythonVenvInterpreter = Path.Combine(env.path, "Scripts", "python.exe");
                    // CallPipFreeze, PYTHONPATH limited to py venv path
                    string pythonPath = Path.Combine(env.path, "Lib", "site-packages");
#else
                    string pythonVenvInterpreter = Path.Combine(env.path, "bin", "python3");
                    string pythonPath            = Path.Combine(env.path, "lib", "site-packages", "python3.7", "site-packages");
#endif
                    // Install several packages:
                    // numpy & vg have no dependencies
                    // UnityPy depends on Brotli colorama lz4 Pillow termcolor
                    var     argsStr = new PyString("-m pip install numpy==1.17.5  vg==1.6.0 UnityPy==1.2.4.8");
                    var     args    = shlex.split(argsStr);
                    dynamic proc    = spawn(pythonVenvInterpreter, args,
                                            wantLogging: false);
                    yield return(WaitForProcessEnd(proc, 60));

                    // Check installations went as expected, to ensure our test is properly set
                    string output = CallPipFreeze(pythonVenvInterpreter, pythonPath);
                    // requested packages with specific versions
                    Assert.That(output, Contains.Substring("numpy==1.17.5"));
                    Assert.That(output, Contains.Substring("vg==1.6.0"));
                    Assert.That(output, Contains.Substring("UnityPy==1.2.4.8"));
                    // dependent packages, we don't know the version number
                    Assert.That(output, Contains.Substring("Brotli"));
                    Assert.That(output, Contains.Substring("colorama"));
                    Assert.That(output, Contains.Substring("lz4"));
                    Assert.That(output, Contains.Substring("Pillow"));
                    Assert.That(output, Contains.Substring("termcolor"));
                    // we should not have any more packages
                    var newLineRegex = new Regex(@"\r\n|\n|\r");
                    var lines        = newLineRegex.Split(output);
                    Assert.That(lines.Length, Is.EqualTo(9)); // 8 package lines + 1 empty line

                    argsStr = new PyString(" -m pip install pip -U");
                    args    = shlex.split(argsStr);

                    dynamic envOverride = new PyDict();
                    envOverride["PYTHONPATH"] = new PyString(pythonPath);
                    dynamic proc2 = spawn(pythonVenvInterpreter, args,
                                          env_override: envOverride,
                                          wantLogging: false);
                    yield return(WaitForProcessEnd(proc2, 20));

                    // Call UpdatePackages with a requirements.txt file containing:
                    // numpy==1.18.2
                    // vg==1.7.0
                    // Brotli==1.0.7
                    var testRequirementFile = Path.Combine(TestsPath, "test_requirements_3.txt");
                    PipPackages.UpdatePackages(testRequirementFile, pythonVenvInterpreter);

                    var output2 = CallPipFreeze(pythonVenvInterpreter, pythonPath);
                    Assert.That(output2, Contains.Substring("numpy==1.18.2"));
                    Assert.That(output2, Contains.Substring("vg==1.7.0"));
                    Assert.That(output2, Contains.Substring("Brotli==1.0.7"));
                    var lines2 = newLineRegex.Split(output2);
                    Assert.That(lines2.Length, Is.EqualTo(4));
                }
            }
        }