1import os
2import sys
3
4import py
5import pytest
6
7import tox
8from tox.interpreters import NoInterpreterInfo
9from tox.session.commands.run.sequential import installpkg, runtestenv
10from tox.venv import (
11    CreationConfig,
12    VirtualEnv,
13    getdigest,
14    prepend_shebang_interpreter,
15    tox_testenv_create,
16    tox_testenv_install_deps,
17)
18
19
20def test_getdigest(tmpdir):
21    assert getdigest(tmpdir) == "0" * 32
22
23
24def test_getsupportedinterpreter(monkeypatch, newconfig, mocksession):
25    config = newconfig(
26        [],
27        """\
28        [testenv:python]
29        basepython={}
30        """.format(
31            sys.executable
32        ),
33    )
34    mocksession.new_config(config)
35    venv = mocksession.getvenv("python")
36    interp = venv.getsupportedinterpreter()
37    # realpath needed for debian symlinks
38    assert py.path.local(interp).realpath() == py.path.local(sys.executable).realpath()
39    monkeypatch.setattr(tox.INFO, "IS_WIN", True)
40    monkeypatch.setattr(venv.envconfig, "basepython", "jython")
41    with pytest.raises(tox.exception.UnsupportedInterpreter):
42        venv.getsupportedinterpreter()
43    monkeypatch.undo()
44    monkeypatch.setattr(venv.envconfig, "envname", "py1")
45    monkeypatch.setattr(venv.envconfig, "basepython", "notexisting")
46    with pytest.raises(tox.exception.InterpreterNotFound):
47        venv.getsupportedinterpreter()
48    monkeypatch.undo()
49    # check that we properly report when no version_info is present
50    info = NoInterpreterInfo(name=venv.name)
51    info.executable = "something"
52    monkeypatch.setattr(config.interpreters, "get_info", lambda *args, **kw: info)
53    with pytest.raises(tox.exception.InvocationError):
54        venv.getsupportedinterpreter()
55
56
57def test_create(mocksession, newconfig):
58    config = newconfig(
59        [],
60        """\
61        [testenv:py123]
62        """,
63    )
64    envconfig = config.envconfigs["py123"]
65    mocksession.new_config(config)
66    venv = mocksession.getvenv("py123")
67    assert venv.path == envconfig.envdir
68    assert not venv.path.check()
69    with mocksession.newaction(venv.name, "getenv") as action:
70        tox_testenv_create(action=action, venv=venv)
71    pcalls = mocksession._pcalls
72    assert len(pcalls) >= 1
73    args = pcalls[0].args
74    assert "virtualenv" == str(args[2])
75    if not tox.INFO.IS_WIN:
76        # realpath is needed for stuff like the debian symlinks
77        our_sys_path = py.path.local(sys.executable).realpath()
78        assert our_sys_path == py.path.local(args[0]).realpath()
79        # assert Envconfig.toxworkdir in args
80        assert venv.getcommandpath("easy_install", cwd=py.path.local())
81    interp = venv._getliveconfig().base_resolved_python_path
82    assert interp == venv.envconfig.python_info.executable
83    assert venv.path_config.check(exists=False)
84
85
86def test_create_KeyboardInterrupt(mocksession, newconfig, mocker):
87    config = newconfig(
88        [],
89        """\
90        [testenv:py123]
91        """,
92    )
93    mocksession.new_config(config)
94    venv = mocksession.getvenv("py123")
95    with mocker.patch.object(venv, "_pcall", side_effect=KeyboardInterrupt):
96        with pytest.raises(KeyboardInterrupt):
97            venv.setupenv()
98
99    assert venv.status == "keyboardinterrupt"
100
101
102def test_commandpath_venv_precedence(tmpdir, monkeypatch, mocksession, newconfig):
103    config = newconfig(
104        [],
105        """\
106        [testenv:py123]
107        """,
108    )
109    mocksession.new_config(config)
110    venv = mocksession.getvenv("py123")
111    envconfig = venv.envconfig
112    tmpdir.ensure("easy_install")
113    monkeypatch.setenv("PATH", str(tmpdir), prepend=os.pathsep)
114    envconfig.envbindir.ensure("easy_install")
115    p = venv.getcommandpath("easy_install")
116    assert py.path.local(p).relto(envconfig.envbindir), p
117
118
119def test_create_sitepackages(mocksession, newconfig):
120    config = newconfig(
121        [],
122        """\
123        [testenv:site]
124        sitepackages=True
125
126        [testenv:nosite]
127        sitepackages=False
128        """,
129    )
130    mocksession.new_config(config)
131    venv = mocksession.getvenv("site")
132    with mocksession.newaction(venv.name, "getenv") as action:
133        tox_testenv_create(action=action, venv=venv)
134    pcalls = mocksession._pcalls
135    assert len(pcalls) >= 1
136    args = pcalls[0].args
137    assert "--system-site-packages" in map(str, args)
138    mocksession._clearmocks()
139
140    venv = mocksession.getvenv("nosite")
141    with mocksession.newaction(venv.name, "getenv") as action:
142        tox_testenv_create(action=action, venv=venv)
143    pcalls = mocksession._pcalls
144    assert len(pcalls) >= 1
145    args = pcalls[0].args
146    assert "--system-site-packages" not in map(str, args)
147    assert "--no-site-packages" not in map(str, args)
148
149
150def test_install_deps_wildcard(newmocksession):
151    mocksession = newmocksession(
152        [],
153        """\
154        [tox]
155        distshare = {toxworkdir}/distshare
156        [testenv:py123]
157        deps=
158            {distshare}/dep1-*
159        """,
160    )
161    venv = mocksession.getvenv("py123")
162    with mocksession.newaction(venv.name, "getenv") as action:
163        tox_testenv_create(action=action, venv=venv)
164        pcalls = mocksession._pcalls
165        assert len(pcalls) == 1
166        distshare = venv.envconfig.config.distshare
167        distshare.ensure("dep1-1.0.zip")
168        distshare.ensure("dep1-1.1.zip")
169
170        tox_testenv_install_deps(action=action, venv=venv)
171        assert len(pcalls) == 2
172        args = pcalls[-1].args
173        assert pcalls[-1].cwd == venv.envconfig.config.toxinidir
174
175    assert py.path.local.sysfind("python") == args[0]
176    assert ["-m", "pip"] == args[1:3]
177    assert args[3] == "install"
178    args = [arg for arg in args if str(arg).endswith("dep1-1.1.zip")]
179    assert len(args) == 1
180
181
182def test_install_deps_indexserver(newmocksession):
183    mocksession = newmocksession(
184        [],
185        """\
186        [tox]
187        indexserver =
188            abc = ABC
189            abc2 = ABC
190        [testenv:py123]
191        deps=
192            dep1
193            :abc:dep2
194            :abc2:dep3
195        """,
196    )
197    venv = mocksession.getvenv("py123")
198    with mocksession.newaction(venv.name, "getenv") as action:
199        tox_testenv_create(action=action, venv=venv)
200        pcalls = mocksession._pcalls
201        assert len(pcalls) == 1
202        pcalls[:] = []
203
204        tox_testenv_install_deps(action=action, venv=venv)
205        # two different index servers, two calls
206        assert len(pcalls) == 3
207        args = " ".join(pcalls[0].args)
208        assert "-i " not in args
209        assert "dep1" in args
210
211        args = " ".join(pcalls[1].args)
212        assert "-i ABC" in args
213        assert "dep2" in args
214        args = " ".join(pcalls[2].args)
215        assert "-i ABC" in args
216        assert "dep3" in args
217
218
219def test_install_deps_pre(newmocksession):
220    mocksession = newmocksession(
221        [],
222        """\
223        [testenv]
224        pip_pre=true
225        deps=
226            dep1
227        """,
228    )
229    venv = mocksession.getvenv("python")
230    with mocksession.newaction(venv.name, "getenv") as action:
231        tox_testenv_create(action=action, venv=venv)
232    pcalls = mocksession._pcalls
233    assert len(pcalls) == 1
234    pcalls[:] = []
235
236    tox_testenv_install_deps(action=action, venv=venv)
237    assert len(pcalls) == 1
238    args = " ".join(pcalls[0].args)
239    assert "--pre " in args
240    assert "dep1" in args
241
242
243def test_installpkg_indexserver(newmocksession, tmpdir):
244    mocksession = newmocksession(
245        [],
246        """\
247        [tox]
248        indexserver =
249            default = ABC
250        """,
251    )
252    venv = mocksession.getvenv("python")
253    pcalls = mocksession._pcalls
254    p = tmpdir.ensure("distfile.tar.gz")
255    installpkg(venv, p)
256    # two different index servers, two calls
257    assert len(pcalls) == 1
258    args = " ".join(pcalls[0].args)
259    assert "-i ABC" in args
260
261
262def test_install_recreate(newmocksession, tmpdir):
263    pkg = tmpdir.ensure("package.tar.gz")
264    mocksession = newmocksession(
265        ["--recreate"],
266        """\
267        [testenv]
268        deps=xyz
269        """,
270    )
271    venv = mocksession.getvenv("python")
272
273    with mocksession.newaction(venv.name, "update") as action:
274        venv.update(action)
275        installpkg(venv, pkg)
276        mocksession.report.expect("verbosity0", "*create*")
277        venv.update(action)
278        mocksession.report.expect("verbosity0", "*recreate*")
279
280
281def test_install_sdist_extras(newmocksession):
282    mocksession = newmocksession(
283        [],
284        """\
285        [testenv]
286        extras = testing
287            development
288        """,
289    )
290    venv = mocksession.getvenv("python")
291    with mocksession.newaction(venv.name, "getenv") as action:
292        tox_testenv_create(action=action, venv=venv)
293    pcalls = mocksession._pcalls
294    assert len(pcalls) == 1
295    pcalls[:] = []
296
297    venv.installpkg("distfile.tar.gz", action=action)
298    assert "distfile.tar.gz[testing,development]" in pcalls[-1].args
299
300
301def test_develop_extras(newmocksession, tmpdir):
302    mocksession = newmocksession(
303        [],
304        """\
305        [testenv]
306        extras = testing
307            development
308        """,
309    )
310    venv = mocksession.getvenv("python")
311    with mocksession.newaction(venv.name, "getenv") as action:
312        tox_testenv_create(action=action, venv=venv)
313    pcalls = mocksession._pcalls
314    assert len(pcalls) == 1
315    pcalls[:] = []
316
317    venv.developpkg(tmpdir, action=action)
318    expected = "{}[testing,development]".format(tmpdir.strpath)
319    assert expected in pcalls[-1].args
320
321
322def test_env_variables_added_to_needs_reinstall(tmpdir, mocksession, newconfig, monkeypatch):
323    tmpdir.ensure("setup.py")
324    monkeypatch.setenv("TEMP_PASS_VAR", "123")
325    monkeypatch.setenv("TEMP_NOPASS_VAR", "456")
326    config = newconfig(
327        [],
328        """\
329        [testenv:python]
330        passenv = temp_pass_var
331        setenv =
332            CUSTOM_VAR = 789
333        """,
334    )
335    mocksession.new_config(config)
336    venv = mocksession.getvenv("python")
337    with mocksession.newaction(venv.name, "hello") as action:
338        venv._needs_reinstall(tmpdir, action)
339
340    pcalls = mocksession._pcalls
341    assert len(pcalls) == 2
342    env = pcalls[0].env
343
344    # should have access to setenv vars
345    assert "CUSTOM_VAR" in env
346    assert env["CUSTOM_VAR"] == "789"
347
348    # should have access to passenv vars
349    assert "TEMP_PASS_VAR" in env
350    assert env["TEMP_PASS_VAR"] == "123"
351
352    # should also have access to full invocation environment,
353    # for backward compatibility, and to match behavior of venv.run_install_command()
354    assert "TEMP_NOPASS_VAR" in env
355    assert env["TEMP_NOPASS_VAR"] == "456"
356
357
358def test_test_hashseed_is_in_output(newmocksession, monkeypatch):
359    seed = "123456789"
360    monkeypatch.setattr("tox.config.make_hashseed", lambda: seed)
361    mocksession = newmocksession([], "")
362    venv = mocksession.getvenv("python")
363    with mocksession.newaction(venv.name, "update") as action:
364        venv.update(action)
365    tox.venv.tox_runtest_pre(venv)
366    mocksession.report.expect("verbosity0", "run-test-pre: PYTHONHASHSEED='{}'".format(seed))
367
368
369def test_test_runtests_action_command_is_in_output(newmocksession):
370    mocksession = newmocksession(
371        [],
372        """\
373        [testenv]
374        commands = echo foo bar
375        """,
376    )
377    venv = mocksession.getvenv("python")
378    with mocksession.newaction(venv.name, "update") as action:
379        venv.update(action)
380    venv.test()
381    mocksession.report.expect("verbosity0", "*run-test:*commands?0? | echo foo bar")
382
383
384def test_install_error(newmocksession):
385    mocksession = newmocksession(
386        ["--recreate"],
387        """\
388        [testenv]
389        deps=xyz
390        commands=
391            qwelkqw
392        """,
393    )
394    venv = mocksession.getvenv("python")
395    venv.test()
396    mocksession.report.expect("error", "*not find*qwelkqw*")
397    assert venv.status == "commands failed"
398
399
400def test_install_command_not_installed(newmocksession):
401    mocksession = newmocksession(
402        ["--recreate"],
403        """\
404        [testenv]
405        commands=
406            pytest
407        """,
408    )
409    venv = mocksession.getvenv("python")
410    venv.status = 0
411    venv.test()
412    mocksession.report.expect("warning", "*test command found but not*")
413    assert venv.status == 0
414
415
416def test_install_command_whitelisted(newmocksession):
417    mocksession = newmocksession(
418        ["--recreate"],
419        """\
420        [testenv]
421        whitelist_externals = pytest
422                              xy*
423        commands=
424            pytest
425            xyz
426        """,
427    )
428    venv = mocksession.getvenv("python")
429    venv.test()
430    mocksession.report.expect("warning", "*test command found but not*", invert=True)
431    assert venv.status == "commands failed"
432
433
434def test_install_command_not_installed_bash(newmocksession):
435    mocksession = newmocksession(
436        ["--recreate"],
437        """\
438        [testenv]
439        commands=
440            bash
441        """,
442    )
443    venv = mocksession.getvenv("python")
444    venv.test()
445    mocksession.report.expect("warning", "*test command found but not*")
446
447
448def test_install_python3(newmocksession):
449    if not py.path.local.sysfind("python3"):
450        pytest.skip("needs python3")
451    mocksession = newmocksession(
452        [],
453        """\
454        [testenv:py123]
455        basepython=python3
456        deps=
457            dep1
458            dep2
459        """,
460    )
461    venv = mocksession.getvenv("py123")
462    with mocksession.newaction(venv.name, "getenv") as action:
463        tox_testenv_create(action=action, venv=venv)
464        pcalls = mocksession._pcalls
465        assert len(pcalls) == 1
466        args = pcalls[0].args
467        assert str(args[2]) == "virtualenv"
468        pcalls[:] = []
469    with mocksession.newaction(venv.name, "hello") as action:
470        venv._install(["hello"], action=action)
471        assert len(pcalls) == 1
472    args = pcalls[0].args
473    assert py.path.local.sysfind("python") == args[0]
474    assert ["-m", "pip"] == args[1:3]
475    for _ in args:
476        assert "--download-cache" not in args, args
477
478
479class TestCreationConfig:
480    def test_basic(self, newconfig, mocksession, tmpdir):
481        config = newconfig([], "")
482        mocksession.new_config(config)
483        venv = mocksession.getvenv("python")
484        cconfig = venv._getliveconfig()
485        assert cconfig.matches(cconfig)
486        path = tmpdir.join("configdump")
487        cconfig.writeconfig(path)
488        newconfig = CreationConfig.readconfig(path)
489        assert newconfig.matches(cconfig)
490        assert cconfig.matches(newconfig)
491
492    def test_matchingdependencies(self, newconfig, mocksession):
493        config = newconfig(
494            [],
495            """\
496            [testenv]
497            deps=abc
498            """,
499        )
500        mocksession.new_config(config)
501        venv = mocksession.getvenv("python")
502        cconfig = venv._getliveconfig()
503        config = newconfig(
504            [],
505            """\
506            [testenv]
507            deps=xyz
508            """,
509        )
510        mocksession.new_config(config)
511        venv = mocksession.getvenv("python")
512        otherconfig = venv._getliveconfig()
513        assert not cconfig.matches(otherconfig)
514
515    def test_matchingdependencies_file(self, newconfig, mocksession):
516        config = newconfig(
517            [],
518            """\
519            [tox]
520            distshare={toxworkdir}/distshare
521            [testenv]
522            deps=abc
523                 {distshare}/xyz.zip
524            """,
525        )
526        xyz = config.distshare.join("xyz.zip")
527        xyz.ensure()
528        mocksession.new_config(config)
529        venv = mocksession.getvenv("python")
530        cconfig = venv._getliveconfig()
531        assert cconfig.matches(cconfig)
532        xyz.write("hello")
533        newconfig = venv._getliveconfig()
534        assert not cconfig.matches(newconfig)
535
536    def test_matchingdependencies_latest(self, newconfig, mocksession):
537        config = newconfig(
538            [],
539            """\
540            [tox]
541            distshare={toxworkdir}/distshare
542            [testenv]
543            deps={distshare}/xyz-*
544            """,
545        )
546        config.distshare.ensure("xyz-1.2.0.zip")
547        xyz2 = config.distshare.ensure("xyz-1.2.1.zip")
548        mocksession.new_config(config)
549        venv = mocksession.getvenv("python")
550        cconfig = venv._getliveconfig()
551        md5, path = cconfig.deps[0]
552        assert path == xyz2
553        assert md5 == path.computehash()
554
555    def test_python_recreation(self, tmpdir, newconfig, mocksession):
556        pkg = tmpdir.ensure("package.tar.gz")
557        config = newconfig(["-v"], "")
558        mocksession.new_config(config)
559        venv = mocksession.getvenv("python")
560        create_config = venv._getliveconfig()
561        with mocksession.newaction(venv.name, "update") as action:
562            venv.update(action)
563            assert not venv.path_config.check()
564        installpkg(venv, pkg)
565        assert venv.path_config.check()
566        assert mocksession._pcalls
567        args1 = map(str, mocksession._pcalls[0].args)
568        assert "virtualenv" in " ".join(args1)
569        mocksession.report.expect("*", "*create*")
570        # modify config and check that recreation happens
571        mocksession._clearmocks()
572        with mocksession.newaction(venv.name, "update") as action:
573            venv.update(action)
574            mocksession.report.expect("*", "*reusing*")
575            mocksession._clearmocks()
576        with mocksession.newaction(venv.name, "update") as action:
577            create_config.base_resolved_python_path = py.path.local("balla")
578            create_config.writeconfig(venv.path_config)
579            venv.update(action)
580            mocksession.report.expect("verbosity0", "*recreate*")
581
582    def test_dep_recreation(self, newconfig, mocksession):
583        config = newconfig([], "")
584        mocksession.new_config(config)
585        venv = mocksession.getvenv("python")
586        with mocksession.newaction(venv.name, "update") as action:
587            venv.update(action)
588            cconfig = venv._getliveconfig()
589            cconfig.deps[:] = [("1" * 32, "xyz.zip")]
590            cconfig.writeconfig(venv.path_config)
591            mocksession._clearmocks()
592        with mocksession.newaction(venv.name, "update") as action:
593            venv.update(action)
594            mocksession.report.expect("*", "*recreate*")
595
596    def test_develop_recreation(self, newconfig, mocksession):
597        config = newconfig([], "")
598        mocksession.new_config(config)
599        venv = mocksession.getvenv("python")
600        with mocksession.newaction(venv.name, "update") as action:
601            venv.update(action)
602            cconfig = venv._getliveconfig()
603            cconfig.usedevelop = True
604            cconfig.writeconfig(venv.path_config)
605            mocksession._clearmocks()
606        with mocksession.newaction(venv.name, "update") as action:
607            venv.update(action)
608            mocksession.report.expect("verbosity0", "*recreate*")
609
610
611class TestVenvTest:
612    def test_envbindir_path(self, newmocksession, monkeypatch):
613        monkeypatch.setenv("PIP_RESPECT_VIRTUALENV", "1")
614        mocksession = newmocksession(
615            [],
616            """\
617            [testenv:python]
618            commands=abc
619            """,
620        )
621        venv = mocksession.getvenv("python")
622        with mocksession.newaction(venv.name, "getenv") as action:
623            monkeypatch.setenv("PATH", "xyz")
624            sysfind_calls = []
625            monkeypatch.setattr(
626                "py.path.local.sysfind",
627                classmethod(lambda *args, **kwargs: sysfind_calls.append(kwargs) or 0 / 0),
628            )
629
630            with pytest.raises(ZeroDivisionError):
631                venv._install(list("123"), action=action)
632            assert sysfind_calls.pop()["paths"] == [venv.envconfig.envbindir]
633            with pytest.raises(ZeroDivisionError):
634                venv.test(action)
635            assert sysfind_calls.pop()["paths"] == [venv.envconfig.envbindir]
636            with pytest.raises(ZeroDivisionError):
637                venv.run_install_command(["qwe"], action=action)
638            assert sysfind_calls.pop()["paths"] == [venv.envconfig.envbindir]
639            monkeypatch.setenv("PIP_RESPECT_VIRTUALENV", "1")
640            monkeypatch.setenv("PIP_REQUIRE_VIRTUALENV", "1")
641            monkeypatch.setenv("__PYVENV_LAUNCHER__", "1")
642
643            prev_pcall = venv._pcall
644
645            def collect(*args, **kwargs):
646                env = kwargs["env"]
647                assert "PIP_RESPECT_VIRTUALENV" not in env
648                assert "PIP_REQUIRE_VIRTUALENV" not in env
649                assert "__PYVENV_LAUNCHER__" not in env
650                assert env["PIP_USER"] == "0"
651                assert env["PIP_NO_DEPS"] == "0"
652                return prev_pcall(*args, **kwargs)
653
654            monkeypatch.setattr(venv, "_pcall", collect)
655            with pytest.raises(ZeroDivisionError):
656                venv.run_install_command(["qwe"], action=action)
657
658    def test_pythonpath_remove(self, newmocksession, monkeypatch, caplog):
659        monkeypatch.setenv("PYTHONPATH", "/my/awesome/library")
660        mocksession = newmocksession(
661            [],
662            """\
663            [testenv:python]
664            commands=abc
665            """,
666        )
667        venv = mocksession.getvenv("python")
668        with mocksession.newaction(venv.name, "getenv") as action:
669            venv.run_install_command(["qwe"], action=action)
670        mocksession.report.expect("warning", "*Discarding $PYTHONPATH from environment*")
671
672        pcalls = mocksession._pcalls
673        assert len(pcalls) == 1
674        assert "PYTHONPATH" not in pcalls[0].env
675
676    def test_pythonpath_keep(self, newmocksession, monkeypatch, caplog):
677        # passenv = PYTHONPATH allows PYTHONPATH to stay in environment
678        monkeypatch.setenv("PYTHONPATH", "/my/awesome/library")
679        mocksession = newmocksession(
680            [],
681            """\
682            [testenv:python]
683            commands=abc
684            passenv = PYTHONPATH
685            """,
686        )
687        venv = mocksession.getvenv("python")
688        with mocksession.newaction(venv.name, "getenv") as action:
689            venv.run_install_command(["qwe"], action=action)
690        mocksession.report.not_expect("warning", "*Discarding $PYTHONPATH from environment*")
691        assert "PYTHONPATH" in os.environ
692
693        pcalls = mocksession._pcalls
694        assert len(pcalls) == 1
695        assert pcalls[0].env["PYTHONPATH"] == "/my/awesome/library"
696
697    def test_pythonpath_empty(self, newmocksession, monkeypatch, caplog):
698        monkeypatch.setenv("PYTHONPATH", "")
699        mocksession = newmocksession(
700            [],
701            """\
702            [testenv:python]
703            commands=abc
704            """,
705        )
706        venv = mocksession.getvenv("python")
707        with mocksession.newaction(venv.name, "getenv") as action:
708            venv.run_install_command(["qwe"], action=action)
709        if sys.version_info < (3, 4):
710            mocksession.report.expect("warning", "*Discarding $PYTHONPATH from environment*")
711        else:
712            with pytest.raises(AssertionError):
713                mocksession.report.expect("warning", "*Discarding $PYTHONPATH from environment*")
714        pcalls = mocksession._pcalls
715        assert len(pcalls) == 1
716        assert "PYTHONPATH" not in pcalls[0].env
717
718
719def test_env_variables_added_to_pcall(tmpdir, mocksession, newconfig, monkeypatch):
720    monkeypatch.delenv("PYTHONPATH", raising=False)
721    pkg = tmpdir.ensure("package.tar.gz")
722    monkeypatch.setenv("X123", "123")
723    monkeypatch.setenv("YY", "456")
724    config = newconfig(
725        [],
726        """\
727        [testenv:python]
728        commands=python -V
729        passenv = x123
730        setenv =
731            ENV_VAR = value
732            PYTHONPATH = value
733        """,
734    )
735    mocksession._clearmocks()
736    mocksession.new_config(config)
737    venv = mocksession.getvenv("python")
738    installpkg(venv, pkg)
739    venv.test()
740
741    pcalls = mocksession._pcalls
742    assert len(pcalls) == 2
743    for x in pcalls:
744        env = x.env
745        assert env is not None
746        assert "ENV_VAR" in env
747        assert env["ENV_VAR"] == "value"
748        assert env["VIRTUAL_ENV"] == str(venv.path)
749        assert env["X123"] == "123"
750        assert "PYTHONPATH" in env
751        assert env["PYTHONPATH"] == "value"
752    # all env variables are passed for installation
753    assert pcalls[0].env["YY"] == "456"
754    assert "YY" not in pcalls[1].env
755
756    assert {"ENV_VAR", "VIRTUAL_ENV", "PYTHONHASHSEED", "X123", "PATH"}.issubset(pcalls[1].env)
757
758    # setenv does not trigger PYTHONPATH warnings
759    mocksession.report.not_expect("warning", "*Discarding $PYTHONPATH from environment*")
760
761    # for e in os.environ:
762    #    assert e in env
763
764
765def test_installpkg_no_upgrade(tmpdir, newmocksession):
766    pkg = tmpdir.ensure("package.tar.gz")
767    mocksession = newmocksession([], "")
768    venv = mocksession.getvenv("python")
769    venv.just_created = True
770    venv.envconfig.envdir.ensure(dir=1)
771    installpkg(venv, pkg)
772    pcalls = mocksession._pcalls
773    assert len(pcalls) == 1
774    assert pcalls[0].args[1:-1] == ["-m", "pip", "install", "--exists-action", "w"]
775
776
777@pytest.mark.parametrize("count, level", [(0, 0), (1, 0), (2, 0), (3, 1), (4, 2), (5, 3), (6, 3)])
778def test_install_command_verbosity(tmpdir, newmocksession, count, level):
779    pkg = tmpdir.ensure("package.tar.gz")
780    mock_session = newmocksession(["-{}".format("v" * count)], "")
781    env = mock_session.getvenv("python")
782    env.just_created = True
783    env.envconfig.envdir.ensure(dir=1)
784    installpkg(env, pkg)
785    pcalls = mock_session._pcalls
786    assert len(pcalls) == 1
787    expected = ["-m", "pip", "install", "--exists-action", "w"] + (["-v"] * level)
788    assert pcalls[0].args[1:-1] == expected
789
790
791def test_installpkg_upgrade(newmocksession, tmpdir):
792    pkg = tmpdir.ensure("package.tar.gz")
793    mocksession = newmocksession([], "")
794    venv = mocksession.getvenv("python")
795    assert not hasattr(venv, "just_created")
796    installpkg(venv, pkg)
797    pcalls = mocksession._pcalls
798    assert len(pcalls) == 1
799    index = pcalls[0].args.index(pkg.basename)
800    assert index >= 0
801    assert "-U" in pcalls[0].args[:index]
802    assert "--no-deps" in pcalls[0].args[:index]
803
804
805def test_run_install_command(newmocksession):
806    mocksession = newmocksession([], "")
807    venv = mocksession.getvenv("python")
808    venv.just_created = True
809    venv.envconfig.envdir.ensure(dir=1)
810    with mocksession.newaction(venv.name, "hello") as action:
811        venv.run_install_command(packages=["whatever"], action=action)
812    pcalls = mocksession._pcalls
813    assert len(pcalls) == 1
814    args = pcalls[0].args
815    assert py.path.local.sysfind("python") == args[0]
816    assert ["-m", "pip"] == args[1:3]
817    assert "install" in args
818    env = pcalls[0].env
819    assert env is not None
820
821
822def test_run_custom_install_command(newmocksession):
823    mocksession = newmocksession(
824        [],
825        """\
826        [testenv]
827        install_command=easy_install {opts} {packages}
828        """,
829    )
830    venv = mocksession.getvenv("python")
831    venv.just_created = True
832    venv.envconfig.envdir.ensure(dir=1)
833    with mocksession.newaction(venv.name, "hello") as action:
834        venv.run_install_command(packages=["whatever"], action=action)
835    pcalls = mocksession._pcalls
836    assert len(pcalls) == 1
837    assert "easy_install" in pcalls[0].args[0]
838    assert pcalls[0].args[1:] == ["whatever"]
839
840
841def test_command_relative_issue36(newmocksession, tmpdir, monkeypatch):
842    mocksession = newmocksession(
843        [],
844        """\
845        [testenv]
846        """,
847    )
848    x = tmpdir.ensure("x")
849    venv = mocksession.getvenv("python")
850    x2 = venv.getcommandpath("./x", cwd=tmpdir)
851    assert x == x2
852    mocksession.report.not_expect("warning", "*test command found but not*")
853    x3 = venv.getcommandpath("/bin/bash", cwd=tmpdir)
854    assert x3 == "/bin/bash"
855    mocksession.report.not_expect("warning", "*test command found but not*")
856    monkeypatch.setenv("PATH", str(tmpdir))
857    x4 = venv.getcommandpath("x", cwd=tmpdir)
858    assert x4.endswith(os.sep + "x")
859    mocksession.report.expect("warning", "*test command found but not*")
860
861
862def test_ignore_outcome_failing_cmd(newmocksession):
863    mocksession = newmocksession(
864        [],
865        """\
866        [testenv]
867        commands=testenv_fail
868        ignore_outcome=True
869        """,
870    )
871
872    venv = mocksession.getvenv("python")
873    venv.test()
874    assert venv.status == "ignored failed command"
875    mocksession.report.expect("warning", "*command failed but result from testenv is ignored*")
876
877
878def test_tox_testenv_create(newmocksession):
879    log = []
880
881    class Plugin:
882        @tox.hookimpl
883        def tox_testenv_create(self, action, venv):
884            assert isinstance(action, tox.session.Action)
885            assert isinstance(venv, VirtualEnv)
886            log.append(1)
887
888        @tox.hookimpl
889        def tox_testenv_install_deps(self, action, venv):
890            assert isinstance(action, tox.session.Action)
891            assert isinstance(venv, VirtualEnv)
892            log.append(2)
893
894    mocksession = newmocksession(
895        [],
896        """\
897        [testenv]
898        commands=testenv_fail
899        ignore_outcome=True
900        """,
901        plugins=[Plugin()],
902    )
903
904    venv = mocksession.getvenv("python")
905    with mocksession.newaction(venv.name, "getenv") as action:
906        venv.update(action=action)
907    assert log == [1, 2]
908
909
910def test_tox_testenv_pre_post(newmocksession):
911    log = []
912
913    class Plugin:
914        @tox.hookimpl
915        def tox_runtest_pre(self):
916            log.append("started")
917
918        @tox.hookimpl
919        def tox_runtest_post(self):
920            log.append("finished")
921
922    mocksession = newmocksession(
923        [],
924        """\
925        [testenv]
926        commands=testenv_fail
927        """,
928        plugins=[Plugin()],
929    )
930
931    venv = mocksession.getvenv("python")
932    venv.status = None
933    assert log == []
934    runtestenv(venv, venv.envconfig.config)
935    assert log == ["started", "finished"]
936
937
938@pytest.mark.skipif("sys.platform == 'win32'", reason="no shebang on Windows")
939def test_tox_testenv_interpret_shebang_empty_instance(tmpdir):
940    testfile = tmpdir.join("check_shebang_empty_instance.py")
941    base_args = [str(testfile), "arg1", "arg2", "arg3"]
942
943    # empty instance
944    testfile.write("")
945    args = prepend_shebang_interpreter(base_args)
946    assert args == base_args
947
948
949@pytest.mark.skipif("sys.platform == 'win32'", reason="no shebang on Windows")
950def test_tox_testenv_interpret_shebang_empty_interpreter(tmpdir):
951    testfile = tmpdir.join("check_shebang_empty_interpreter.py")
952    base_args = [str(testfile), "arg1", "arg2", "arg3"]
953
954    # empty interpreter
955    testfile.write("#!")
956    args = prepend_shebang_interpreter(base_args)
957    assert args == base_args
958
959
960@pytest.mark.skipif("sys.platform == 'win32'", reason="no shebang on Windows")
961def test_tox_testenv_interpret_shebang_empty_interpreter_ws(tmpdir):
962    testfile = tmpdir.join("check_shebang_empty_interpreter_ws.py")
963    base_args = [str(testfile), "arg1", "arg2", "arg3"]
964
965    # empty interpreter (whitespaces)
966    testfile.write("#!    \n")
967    args = prepend_shebang_interpreter(base_args)
968    assert args == base_args
969
970
971@pytest.mark.skipif("sys.platform == 'win32'", reason="no shebang on Windows")
972def test_tox_testenv_interpret_shebang_non_utf8(tmpdir):
973    testfile = tmpdir.join("check_non_utf8.py")
974    base_args = [str(testfile), "arg1", "arg2", "arg3"]
975
976    testfile.write_binary(b"#!\x9a\xef\x12\xaf\n")
977    args = prepend_shebang_interpreter(base_args)
978    assert args == base_args
979
980
981@pytest.mark.skipif("sys.platform == 'win32'", reason="no shebang on Windows")
982def test_tox_testenv_interpret_shebang_interpreter_simple(tmpdir):
983    testfile = tmpdir.join("check_shebang_interpreter_simple.py")
984    base_args = [str(testfile), "arg1", "arg2", "arg3"]
985
986    # interpreter (simple)
987    testfile.write("#!interpreter")
988    args = prepend_shebang_interpreter(base_args)
989    assert args == ["interpreter"] + base_args
990
991
992@pytest.mark.skipif("sys.platform == 'win32'", reason="no shebang on Windows")
993def test_tox_testenv_interpret_shebang_interpreter_ws(tmpdir):
994    testfile = tmpdir.join("check_shebang_interpreter_ws.py")
995    base_args = [str(testfile), "arg1", "arg2", "arg3"]
996
997    # interpreter (whitespaces)
998    testfile.write("#!  interpreter  \n\n")
999    args = prepend_shebang_interpreter(base_args)
1000    assert args == ["interpreter"] + base_args
1001
1002
1003@pytest.mark.skipif("sys.platform == 'win32'", reason="no shebang on Windows")
1004def test_tox_testenv_interpret_shebang_interpreter_arg(tmpdir):
1005    testfile = tmpdir.join("check_shebang_interpreter_arg.py")
1006    base_args = [str(testfile), "arg1", "arg2", "arg3"]
1007
1008    # interpreter with argument
1009    testfile.write("#!interpreter argx\n")
1010    args = prepend_shebang_interpreter(base_args)
1011    assert args == ["interpreter", "argx"] + base_args
1012
1013
1014@pytest.mark.skipif("sys.platform == 'win32'", reason="no shebang on Windows")
1015def test_tox_testenv_interpret_shebang_interpreter_args(tmpdir):
1016    testfile = tmpdir.join("check_shebang_interpreter_args.py")
1017    base_args = [str(testfile), "arg1", "arg2", "arg3"]
1018
1019    # interpreter with argument (ensure single argument)
1020    testfile.write("#!interpreter argx argx-part2\n")
1021    args = prepend_shebang_interpreter(base_args)
1022    assert args == ["interpreter", "argx argx-part2"] + base_args
1023
1024
1025@pytest.mark.skipif("sys.platform == 'win32'", reason="no shebang on Windows")
1026def test_tox_testenv_interpret_shebang_real(tmpdir):
1027    testfile = tmpdir.join("check_shebang_real.py")
1028    base_args = [str(testfile), "arg1", "arg2", "arg3"]
1029
1030    # interpreter (real example)
1031    testfile.write("#!/usr/bin/env python\n")
1032    args = prepend_shebang_interpreter(base_args)
1033    assert args == ["/usr/bin/env", "python"] + base_args
1034
1035
1036@pytest.mark.skipif("sys.platform == 'win32'", reason="no shebang on Windows")
1037def test_tox_testenv_interpret_shebang_long_example(tmpdir):
1038    testfile = tmpdir.join("check_shebang_long_example.py")
1039    base_args = [str(testfile), "arg1", "arg2", "arg3"]
1040
1041    # interpreter (long example)
1042    testfile.write(
1043        "#!this-is-an-example-of-a-very-long-interpret-directive-what-should-"
1044        "be-directly-invoked-when-tox-needs-to-invoked-the-provided-script-"
1045        "name-in-the-argument-list"
1046    )
1047    args = prepend_shebang_interpreter(base_args)
1048    expected = [
1049        "this-is-an-example-of-a-very-long-interpret-directive-what-should-be-"
1050        "directly-invoked-when-tox-needs-to-invoked-the-provided-script-name-"
1051        "in-the-argument-list"
1052    ]
1053
1054    assert args == expected + base_args
1055
1056
1057@pytest.mark.parametrize("download", [True, False, None])
1058def test_create_download(mocksession, newconfig, download):
1059    config = newconfig(
1060        [],
1061        """\
1062        [testenv:env]
1063        {}
1064        """.format(
1065            "download={}".format(download) if download else ""
1066        ),
1067    )
1068    mocksession.new_config(config)
1069    venv = mocksession.getvenv("env")
1070    with mocksession.newaction(venv.name, "getenv") as action:
1071        tox_testenv_create(action=action, venv=venv)
1072    pcalls = mocksession._pcalls
1073    assert len(pcalls) >= 1
1074    args = pcalls[0].args
1075    if download is True:
1076        assert "--no-download" not in map(str, args)
1077    else:
1078        assert "--no-download" in map(str, args)
1079    mocksession._clearmocks()
1080