diff options
author | Giampaolo Rodola <g.rodola@gmail.com> | 2020-04-30 12:20:05 +0200 |
---|---|---|
committer | Giampaolo Rodola <g.rodola@gmail.com> | 2020-04-30 12:20:05 +0200 |
commit | c5095d7366d806583df565b364b71cf2aba59fed (patch) | |
tree | ea9da05d947a7824e1bbac02a0a222cdeba43a31 | |
parent | 93c1138ca81b8bf5edd250fc470e1a5770dbdcdd (diff) | |
download | psutil-c5095d7366d806583df565b364b71cf2aba59fed.tar.gz |
See: #1709: allow per-test parallelization
Refactor test runner.py with a saner unittest-based class hierarchy so
that --parallel args affects all test suites (all, by-name, failed).
Also change Makefile which now can be used like this:
make test-process ARGS=--parallel
-rwxr-xr-x | .ci/travis/run.sh | 2 | ||||
-rw-r--r-- | .cirrus.yml | 4 | ||||
-rw-r--r-- | HISTORY.rst | 2 | ||||
-rw-r--r-- | Makefile | 49 | ||||
-rwxr-xr-x | psutil/tests/runner.py | 238 | ||||
-rwxr-xr-x | psutil/tests/test_osx.py | 40 | ||||
-rwxr-xr-x | psutil/tests/test_testutils.py | 11 | ||||
-rwxr-xr-x | scripts/internal/winmake.py | 11 |
8 files changed, 187 insertions, 170 deletions
diff --git a/.ci/travis/run.sh b/.ci/travis/run.sh index 562564b0..879e78a6 100755 --- a/.ci/travis/run.sh +++ b/.ci/travis/run.sh @@ -22,7 +22,7 @@ python setup.py develop if [[ $PYVER == '2.7' ]] && [[ "$(uname -s)" != 'Darwin' ]]; then PSUTIL_TESTING=1 python -Wa -m coverage run psutil/tests/runner.py else - PSUTIL_TESTING=1 python -Wa psutil/tests/runner.py --parallel + PSUTIL_TESTING=1 python -Wa psutil/tests/runner.py fi if [ "$PYVER" == "2.7" ] || [ "$PYVER" == "3.6" ]; then diff --git a/.cirrus.yml b/.cirrus.yml index 129644c5..4b8676bc 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -9,7 +9,7 @@ freebsd_13_py3_task: - python3 -m pip install --user setuptools concurrencytest - make clean - make install - - make test-parallel + - make test - make test-memleaks - make print-access-denied - make print-api-speed @@ -25,7 +25,7 @@ freebsd_11_py2_task: - python2.7 -m pip install --user setuptools ipaddress mock concurrencytest - make clean - make install - - make test-parallel + - make test - make test-memleaks - make print-access-denied - make print-api-speed diff --git a/HISTORY.rst b/HISTORY.rst index 7b992a8c..c375934c 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -10,6 +10,8 @@ XXXX-XX-XX - 1729_: parallel tests on UNIX (make test-parallel). - 1736_: psutil.Popen now inherits from subprocess.Popen instead of psutil.Process. Also, wait(timeout=...) parameter is backported to Python 2.7. +- 1741_: "make build/install" is now run in parallel and it's about 15% faster + on UNIX. **Bug fixes** @@ -2,10 +2,12 @@ # To use a specific Python version run: "make install PYTHON=python3.3" # You can set the variables below from the command line. +# Configurable. PYTHON = python3 -TSCRIPT = psutil/tests/runner.py ARGS = -# List of nice-to-have dev libs. +TSCRIPT = psutil/tests/runner.py + +# Internal. DEPS = \ argparse \ check-manifest \ @@ -26,6 +28,12 @@ PY2_DEPS = \ unittest2 DEPS += `$(PYTHON) -c \ "import sys; print('$(PY2_DEPS)' if sys.version_info[0] == 2 else '')"` +# "python3 setup.py build" can be parallelized on Python >= 3.6. +BUILD_OPTS = `$(PYTHON) -c \ + "import sys, os; \ + py36 = sys.version_info[:2] >= (3, 6); \ + cpus = os.cpu_count() or 1 if py36 else 1; \ + print('--parallel %s' % cpus if cpus > 1 else '')"` # In not in a virtualenv, add --user options for install commands. INSTALL_OPTS = `$(PYTHON) -c \ "import sys; print('' if hasattr(sys, 'real_prefix') else '--user')"` @@ -62,14 +70,13 @@ clean: ## Remove all build files. _: -build: _ ## Compile without installing. +build: _ ## Compile (in parallel) without installing. # make sure setuptools is installed (needed for 'develop' / edit mode) $(PYTHON) -c "import setuptools" - PYTHONWARNINGS=all $(PYTHON) setup.py build - @# copies compiled *.so files in ./psutil directory in order to allow - @# "import psutil" when using the interactive interpreter from within - @# this directory. - PYTHONWARNINGS=all $(PYTHON) setup.py build_ext -i + @# "build_ext -i" copies compiled *.so files in ./psutil directory in order + @# to allow "import psutil" when using the interactive interpreter from + @# within this directory. + PYTHONWARNINGS=all $(PYTHON) setup.py build_ext -i $(BUILD_OPTS) $(PYTHON) -c "import psutil" # make sure it actually worked install: ## Install this package as current user in "edit" mode. @@ -111,51 +118,51 @@ setup-dev-env: ## Install GIT hooks, pip, test deps (also upgrades them). test: ## Run all tests. ${MAKE} install - $(TEST_PREFIX) $(PYTHON) $(TSCRIPT) + $(TEST_PREFIX) $(PYTHON) $(TSCRIPT) $(ARGS) test-parallel: ## Run all tests in parallel. ${MAKE} install - $(TEST_PREFIX) $(PYTHON) $(TSCRIPT) --parallel + $(TEST_PREFIX) $(PYTHON) $(TSCRIPT) $(ARGS) --parallel test-process: ## Run process-related API tests. ${MAKE} install - $(TEST_PREFIX) $(PYTHON) psutil/tests/test_process.py + $(TEST_PREFIX) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_process.py test-system: ## Run system-related API tests. ${MAKE} install - $(TEST_PREFIX) $(PYTHON) psutil/tests/test_system.py + $(TEST_PREFIX) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_system.py test-misc: ## Run miscellaneous tests. ${MAKE} install - $(TEST_PREFIX) $(PYTHON) psutil/tests/test_misc.py + $(TEST_PREFIX) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_misc.py test-testutils: ## Run test utils tests. ${MAKE} install - $(TEST_PREFIX) $(PYTHON) psutil/tests/test_testutils.py + $(TEST_PREFIX) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_testutils.py test-unicode: ## Test APIs dealing with strings. ${MAKE} install - $(TEST_PREFIX) $(PYTHON) psutil/tests/test_unicode.py + $(TEST_PREFIX) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_unicode.py test-contracts: ## APIs sanity tests. ${MAKE} install - $(TEST_PREFIX) $(PYTHON) psutil/tests/test_contracts.py + $(TEST_PREFIX) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_contracts.py test-connections: ## Test net_connections() and Process.connections(). ${MAKE} install - $(TEST_PREFIX) $(PYTHON) psutil/tests/test_connections.py + $(TEST_PREFIX) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_connections.py test-posix: ## POSIX specific tests. ${MAKE} install - $(TEST_PREFIX) $(PYTHON) psutil/tests/test_posix.py + $(TEST_PREFIX) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_posix.py test-platform: ## Run specific platform tests only. ${MAKE} install - $(TEST_PREFIX) $(PYTHON) psutil/tests/test_`$(PYTHON) -c 'import psutil; print([x.lower() for x in ("LINUX", "BSD", "OSX", "SUNOS", "WINDOWS", "AIX") if getattr(psutil, x)][0])'`.py + $(TEST_PREFIX) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_`$(PYTHON) -c 'import psutil; print([x.lower() for x in ("LINUX", "BSD", "OSX", "SUNOS", "WINDOWS", "AIX") if getattr(psutil, x)][0])'`.py test-memleaks: ## Memory leak tests. ${MAKE} install - $(TEST_PREFIX) $(PYTHON) psutil/tests/test_memory_leaks.py + $(TEST_PREFIX) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_memory_leaks.py test-by-name: ## e.g. make test-by-name ARGS=psutil.tests.test_system.TestSystemAPIs ${MAKE} install @@ -163,7 +170,7 @@ test-by-name: ## e.g. make test-by-name ARGS=psutil.tests.test_system.TestSyste test-failed: ## Re-run tests which failed on last run ${MAKE} install - $(TEST_PREFIX) $(PYTHON) $(TSCRIPT) --last-failed + $(TEST_PREFIX) $(PYTHON) $(TSCRIPT) $(ARGS) --last-failed test-coverage: ## Run test coverage. ${MAKE} install diff --git a/psutil/tests/runner.py b/psutil/tests/runner.py index 8e4c872a..ef135c8d 100755 --- a/psutil/tests/runner.py +++ b/psutil/tests/runner.py @@ -10,6 +10,14 @@ Unit test runner, providing new features on top of unittest module: - parallel run (UNIX only) - print failures/tracebacks on CTRL+C - re-run failed tests only (make test-failed) + +Invocation examples: +- make test +- make test-failed + +Parallel: +- make test-parallel +- make test-process ARGS=--parallel """ from __future__ import print_function @@ -20,9 +28,6 @@ import sys import textwrap import time import unittest -from unittest import TestResult -from unittest import TextTestResult -from unittest import TextTestRunner try: import ctypes except ImportError: @@ -37,6 +42,7 @@ import psutil from psutil._common import hilite from psutil._common import print_color from psutil._common import term_supports_colors +from psutil._compat import super from psutil.tests import APPVEYOR from psutil.tests import import_module_by_path from psutil.tests import reap_children @@ -44,61 +50,15 @@ from psutil.tests import safe_rmpath from psutil.tests import TOX -HERE = os.path.abspath(os.path.dirname(__file__)) VERBOSITY = 1 if TOX else 2 FAILED_TESTS_FNAME = '.failed-tests.txt' NWORKERS = psutil.cpu_count() or 1 +HERE = os.path.abspath(os.path.dirname(__file__)) loadTestsFromTestCase = unittest.defaultTestLoader.loadTestsFromTestCase -# ===================================================================== -# --- unittest subclasses -# ===================================================================== - - -class ColouredResult(TextTestResult): - - def _print_color(self, s, color, bold=False): - print_color(s, color, bold=bold, file=self.stream) - - def addSuccess(self, test): - TestResult.addSuccess(self, test) - self._print_color("OK", "green") - - def addError(self, test, err): - TestResult.addError(self, test, err) - self._print_color("ERROR", "red", bold=True) - - def addFailure(self, test, err): - TestResult.addFailure(self, test, err) - self._print_color("FAIL", "red") - - def addSkip(self, test, reason): - TestResult.addSkip(self, test, reason) - self._print_color("skipped: %s" % reason.strip(), "brown") - - def printErrorList(self, flavour, errors): - flavour = hilite(flavour, "red", bold=flavour == 'ERROR') - TextTestResult.printErrorList(self, flavour, errors) - - -class ColouredTextRunner(TextTestRunner): - resultclass = ColouredResult - - def _makeResult(self): - # Store result instance so that it can be accessed on - # KeyboardInterrupt. - self.result = TextTestRunner._makeResult(self) - return self.result - - -# ===================================================================== -# --- public API -# ===================================================================== - - -class SuiteLoader: +class TestLoader: testdir = HERE skip_files = ['test_memory_leaks.py'] @@ -130,17 +90,6 @@ class SuiteLoader: suite.addTest(test) return suite - def parallel(self): - serial = unittest.TestSuite() - parallel = unittest.TestSuite() - for obj in self._iter_testmod_classes(): - test = loadTestsFromTestCase(obj) - if getattr(obj, '_serialrun', False): - serial.addTest(test) - else: - parallel.addTest(test) - return (serial, parallel) - def last_failed(self): # ...from previously failed test run suite = unittest.TestSuite() @@ -154,22 +103,57 @@ class SuiteLoader: return suite def from_name(self, name): - suite = unittest.TestSuite() if name.endswith('.py'): name = os.path.splitext(os.path.basename(name))[0] - suite.addTest(unittest.defaultTestLoader.loadTestsFromName(name)) - return suite + return unittest.defaultTestLoader.loadTestsFromName(name) + + +class ColouredResult(unittest.TextTestResult): + + def _print_color(self, s, color, bold=False): + print_color(s, color, bold=bold, file=self.stream) + + def addSuccess(self, test): + unittest.TestResult.addSuccess(self, test) + self._print_color("OK", "green") + + def addError(self, test, err): + unittest.TestResult.addError(self, test, err) + self._print_color("ERROR", "red", bold=True) + + def addFailure(self, test, err): + unittest.TestResult.addFailure(self, test, err) + self._print_color("FAIL", "red") + + def addSkip(self, test, reason): + unittest.TestResult.addSkip(self, test, reason) + self._print_color("skipped: %s" % reason.strip(), "brown") + + def printErrorList(self, flavour, errors): + flavour = hilite(flavour, "red", bold=flavour == 'ERROR') + super().printErrorList(flavour, errors) -class Runner: +class ColouredTextRunner(unittest.TextTestRunner): + """ + A coloured text runner which also prints failed tests on KeyboardInterrupt + and save failed tests in a file so that they can be re-run. + """ + + if term_supports_colors() and not APPVEYOR: + resultclass = ColouredResult + else: + resultclass = unittest.TextTestResult - def __init__(self): - self.loader = SuiteLoader() + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.failed_tnames = set() - if term_supports_colors() and not APPVEYOR: - self.runner = ColouredTextRunner(verbosity=VERBOSITY) - else: - self.runner = TextTestRunner(verbosity=VERBOSITY) + + def _makeResult(self): + # Store result instance so that it can be accessed on + # KeyboardInterrupt. + self.result = super()._makeResult() + return self.result def _write_last_failed(self): if self.failed_tnames: @@ -185,7 +169,7 @@ class Runner: def _run(self, suite): try: - result = self.runner.run(suite) + result = super().run(suite) except (KeyboardInterrupt, SystemExit): result = self.runner.result result.printErrors() @@ -194,32 +178,25 @@ class Runner: self._save_result(result) return result - def _finalize(self, success): + def _exit(self, success): if success: + print_color("SUCCESS", "green", bold=True) safe_rmpath(FAILED_TESTS_FNAME) + sys.exit(0) else: + print_color("FAILED", "red", bold=True) self._write_last_failed() - print_color("FAILED", "red") sys.exit(1) - def run(self, suite=None): - """Run tests serially (1 process).""" - if suite is None: - suite = self.loader.all() + def run(self, suite): result = self._run(suite) - self._finalize(result.wasSuccessful()) + self._exit(result.wasSuccessful()) - def run_last_failed(self): - """Run tests which failed in the last run.""" - self.run(self.loader.last_failed()) - def run_from_name(self, name): - """Run test by name, e.g.: - "test_linux.TestSystemCPUStats.test_ctx_switches" - """ - self.run(self.loader.from_name(name)) +class ParallelRunner(ColouredTextRunner): - def _parallelize_suite(self, suite): + @staticmethod + def _parallelize(suite): def fdopen(*args, **kwds): stream = orig_fdopen(*args, **kwds) atexit.register(stream.close) @@ -232,18 +209,33 @@ class Runner: forker = concurrencytest.fork_for_tests(NWORKERS) return concurrencytest.ConcurrentTestSuite(suite, forker) - def run_parallel(self): - """Run tests in parallel.""" - ser_suite, par_suite = self.loader.parallel() - par_suite = self._parallelize_suite(par_suite) + @staticmethod + def _split_suite(suite): + serial = unittest.TestSuite() + parallel = unittest.TestSuite() + for test in suite._tests: + if test.countTestCases() == 0: + continue + test_class = test._tests[0].__class__ + if getattr(test_class, '_serialrun', False): + serial.addTest(loadTestsFromTestCase(test_class)) + else: + parallel.addTest(loadTestsFromTestCase(test_class)) + return (serial, parallel) + + def run(self, suite): + ser_suite, par_suite = self._split_suite(suite) + par_suite = self._parallelize(par_suite) # run parallel - print("starting parallel tests using %s workers" % NWORKERS) + print_color("starting parallel tests using %s workers" % NWORKERS, + "green", bold=True) t = time.time() par = self._run(par_suite) par_elapsed = time.time() - t - # cleanup workers and test subprocesses + # At this point we should have N zombies (the workers), which + # will disappear with wait(). orphans = psutil.Process().children() gone, alive = psutil.wait_procs(orphans, timeout=1) if alive: @@ -256,7 +248,7 @@ class Runner: ser_elapsed = time.time() - t # print - if not par.wasSuccessful(): + if not par.wasSuccessful() and ser_suite.countTestCases() > 0: par.printErrors() # print them again at the bottom par_fails, par_errs, par_skips = map(len, (par.failures, par.errors, @@ -264,7 +256,6 @@ class Runner: ser_fails, ser_errs, ser_skips = map(len, (ser.failures, ser.errors, ser.skipped)) - print("-" * 70) print(textwrap.dedent(""" +----------+----------+----------+----------+----------+----------+ | | total | failures | errors | skipped | time | @@ -278,14 +269,33 @@ class Runner: print("Ran %s tests in %.3fs using %s workers" % ( par.testsRun + ser.testsRun, par_elapsed + ser_elapsed, NWORKERS)) ok = par.wasSuccessful() and ser.wasSuccessful() - self._finalize(ok) + self._exit(ok) + + +def get_runner(parallel=False): + def warn(msg): + print_color(msg + " Running serial tests instead.", + "red", file=sys.stderr) + if parallel: + if psutil.WINDOWS: + warn("Can't run parallel tests on Windows.") + elif concurrencytest is None: + warn("concurrencytest module is not installed.") + elif NWORKERS == 1: + warn("Only 1 CPU available.") + else: + return ParallelRunner(verbosity=VERBOSITY) + return ColouredTextRunner(verbosity=VERBOSITY) -runner = Runner() -run_from_name = runner.run_from_name +# Used by test_*,py modules. +def run_from_name(name): + suite = TestLoader().from_name(name) + runner = get_runner() + runner.run(suite) -def _setup(): +def setup(): if 'PSUTIL_TESTING' not in os.environ: # This won't work on Windows but set_testing() below will do it. os.environ['PSUTIL_TESTING'] = '1' @@ -293,7 +303,7 @@ def _setup(): def main(): - _setup() + setup() usage = "python3 -m psutil.tests [opts] [test-name]" parser = optparse.OptionParser(usage=usage, description="run unit tests") parser.add_option("--last-failed", @@ -307,26 +317,22 @@ def main(): if not opts.last_failed: safe_rmpath(FAILED_TESTS_FNAME) - # test-by-name + # loader + loader = TestLoader() if args: if len(args) > 1: parser.print_usage() return sys.exit(1) - return runner.run_from_name(args[0]) + else: + suite = loader.from_name(args[0]) elif opts.last_failed: - runner.run_last_failed() - elif not opts.parallel: - runner.run() - # parallel - elif concurrencytest is None: - print_color("concurrencytest module is not installed; " - "running serial tests instead", "red") - runner.run() - elif NWORKERS == 1: - print_color("only 1 CPU; running serial tests instead", "red") - runner.run() + suite = loader.last_failed() else: - runner.run_parallel() + suite = loader.all() + + # runner + runner = get_runner(opts.parallel) + runner.run(suite) if __name__ == '__main__': diff --git a/psutil/tests/test_osx.py b/psutil/tests/test_osx.py index 4fa8d0af..4df6a884 100755 --- a/psutil/tests/test_osx.py +++ b/psutil/tests/test_osx.py @@ -100,63 +100,63 @@ class TestProcess(unittest.TestCase): time.strftime("%Y", time.localtime(start_psutil))) +# TODO: probably needs removal (duplicate) @unittest.skipIf(not MACOS, "MACOS only") class TestZombieProcessAPIs(unittest.TestCase): @classmethod def setUpClass(cls): cls.parent, cls.zombie = create_zombie_proc() - cls.p = psutil.Process(cls.zombie.pid) @classmethod def tearDownClass(cls): + terminate(cls.parent) terminate(cls.zombie) - terminate(cls.parent) # executed first def test_pidtask_info(self): - self.assertEqual(self.p.status(), psutil.STATUS_ZOMBIE) - self.p.ppid() - self.p.uids() - self.p.gids() - self.p.terminal() - self.p.create_time() + self.assertEqual(self.zombie.status(), psutil.STATUS_ZOMBIE) + self.zombie.ppid() + self.zombie.uids() + self.zombie.gids() + self.zombie.terminal() + self.zombie.create_time() def test_exe(self): - self.assertRaises(psutil.ZombieProcess, self.p.exe) + self.assertRaises(psutil.ZombieProcess, self.zombie.exe) def test_cmdline(self): - self.assertRaises(psutil.ZombieProcess, self.p.cmdline) + self.assertRaises(psutil.ZombieProcess, self.zombie.cmdline) def test_environ(self): - self.assertRaises(psutil.ZombieProcess, self.p.environ) + self.assertRaises(psutil.ZombieProcess, self.zombie.environ) def test_cwd(self): - self.assertRaises(psutil.ZombieProcess, self.p.cwd) + self.assertRaises(psutil.ZombieProcess, self.zombie.cwd) def test_memory_full_info(self): - self.assertRaises(psutil.ZombieProcess, self.p.memory_full_info) + self.assertRaises(psutil.ZombieProcess, self.zombie.memory_full_info) def test_cpu_times(self): - self.assertRaises(psutil.ZombieProcess, self.p.cpu_times) + self.assertRaises(psutil.ZombieProcess, self.zombie.cpu_times) def test_num_ctx_switches(self): - self.assertRaises(psutil.ZombieProcess, self.p.num_ctx_switches) + self.assertRaises(psutil.ZombieProcess, self.zombie.num_ctx_switches) def test_num_threads(self): - self.assertRaises(psutil.ZombieProcess, self.p.num_threads) + self.assertRaises(psutil.ZombieProcess, self.zombie.num_threads) def test_open_files(self): - self.assertRaises(psutil.ZombieProcess, self.p.open_files) + self.assertRaises(psutil.ZombieProcess, self.zombie.open_files) def test_connections(self): - self.assertRaises(psutil.ZombieProcess, self.p.connections) + self.assertRaises(psutil.ZombieProcess, self.zombie.connections) def test_num_fds(self): - self.assertRaises(psutil.ZombieProcess, self.p.num_fds) + self.assertRaises(psutil.ZombieProcess, self.zombie.num_fds) def test_threads(self): self.assertRaises((psutil.ZombieProcess, psutil.AccessDenied), - self.p.threads) + self.zombie.threads) @unittest.skipIf(not MACOS, "MACOS only") diff --git a/psutil/tests/test_testutils.py b/psutil/tests/test_testutils.py index b2be93ff..85b61aea 100755 --- a/psutil/tests/test_testutils.py +++ b/psutil/tests/test_testutils.py @@ -267,11 +267,12 @@ class TestProcessUtils(ProcessTestCase): assert not psutil.pid_exists(pid) terminate(pid) # zombie - parent, zombie = self.create_zombie_proc() - terminate(parent) - terminate(zombie) - assert not psutil.pid_exists(parent.pid) - assert not psutil.pid_exists(zombie.pid) + if POSIX: + parent, zombie = self.create_zombie_proc() + terminate(parent) + terminate(zombie) + assert not psutil.pid_exists(parent.pid) + assert not psutil.pid_exists(zombie.pid) class TestNetUtils(unittest.TestCase): diff --git a/scripts/internal/winmake.py b/scripts/internal/winmake.py index c9aa2952..f39d45ac 100755 --- a/scripts/internal/winmake.py +++ b/scripts/internal/winmake.py @@ -227,8 +227,13 @@ def build(): # edit mode). sh('%s -c "import setuptools"' % PYTHON) + # "build_ext -i" copies compiled *.pyd files in ./psutil directory in + # order to allow "import psutil" when using the interactive interpreter + # from within psutil root directory. + cmd = [PYTHON, "setup.py", "build_ext", "-i"] + if sys.version_info[:2] >= (3, 6) and os.cpu_count() or 1 > 1: + cmd += ['--parallel', str(os.cpu_count())] # Print coloured warnings in real time. - cmd = [PYTHON, "setup.py", "build"] p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) try: for line in iter(p.stdout.readline, b''): @@ -250,10 +255,6 @@ def build(): p.terminate() p.wait() - # Copies compiled *.pyd files in ./psutil directory in order to - # allow "import psutil" when using the interactive interpreter - # from within this directory. - sh("%s setup.py build_ext -i" % PYTHON) # Make sure it actually worked. sh('%s -c "import psutil"' % PYTHON) win_colorprint("build + import successful", GREEN) |