From 2580e08b8c859767ad8dd10e927a38028780288d Mon Sep 17 00:00:00 2001 From: Jeremy Bowman Date: Tue, 24 Jul 2018 16:37:34 -0400 Subject: [PATCH] TE-2659 Fix coverage with remote xdist workers --- pavelib/utils/envs.py | 29 ++++++++++++++++ pavelib/utils/test/suites/pytest_suite.py | 40 +++++++++++++++-------- 2 files changed, 56 insertions(+), 13 deletions(-) diff --git a/pavelib/utils/envs.py b/pavelib/utils/envs.py index 14937d21957c..639b92fbe555 100644 --- a/pavelib/utils/envs.py +++ b/pavelib/utils/envs.py @@ -12,6 +12,7 @@ from lazy import lazy from path import Path as path from paver.easy import sh +from six.moves import configparser from pavelib.utils.cmd import django_cmd @@ -251,6 +252,22 @@ def get_django_setting(cls, django_setting, system, settings=None): ) return unicode(value).strip() + @classmethod + def covered_modules(cls): + """ + List the source modules listed in .coveragerc for which coverage + will be measured. + """ + coveragerc = configparser.RawConfigParser() + coveragerc.read(cls.PYTHON_COVERAGERC) + modules = coveragerc.get('run', 'source') + result = [] + for module in modules.split('\n'): + module = module.strip() + if module: + result.append(module) + return result + @lazy def env_tokens(self): """ @@ -295,3 +312,15 @@ def feature_flags(self): Return a dictionary of feature flags configured by the environment. """ return self.env_tokens.get('FEATURES', dict()) + + @classmethod + def rsync_dirs(cls): + """ + List the directories that should be synced during pytest-xdist + execution. Needs to include all modules for which coverage is + measured, not just the tests being run. + """ + result = set() + for module in cls.covered_modules(): + result.add(module.split('/')[0]) + return result diff --git a/pavelib/utils/test/suites/pytest_suite.py b/pavelib/utils/test/suites/pytest_suite.py index ab320f43fe93..6f2cc120a8e8 100644 --- a/pavelib/utils/test/suites/pytest_suite.py +++ b/pavelib/utils/test/suites/pytest_suite.py @@ -119,6 +119,23 @@ def __init__(self, *args, **kwargs): self.processes = int(self.processes) + def _under_coverage_cmd(self, cmd): + """ + If self.run_under_coverage is True, it returns the arg 'cmd' + altered to be run under coverage. It returns the command + unaltered otherwise. + """ + if self.run_under_coverage: + if self.xdist_ip_addresses: + for module in Env.covered_modules(): + cmd.append('--cov') + cmd.append(module) + else: + cmd.append('--cov') + cmd.append('--cov-report=') + + return cmd + @property def cmd(self): if self.django_toxenv: @@ -148,12 +165,8 @@ def cmd(self): xdist_string = '--tx ssh=ubuntu@{}//python="source /edx/app/edxapp/edxapp_env; ' \ 'python"//chdir="/edx/app/edxapp/edx-platform"'.format(ip) cmd.append(xdist_string) - already_synced_dirs = set() - for test_path in self.test_id.split(): - test_root_dir = test_path.split('/')[0] - if test_root_dir not in already_synced_dirs: - cmd.append('--rsyncdir {}'.format(test_root_dir)) - already_synced_dirs.add(test_root_dir) + for rsync_dir in Env.rsync_dirs(): + cmd.append('--rsyncdir {}'.format(rsync_dir)) else: if self.processes == -1: cmd.append('-n auto') @@ -256,12 +269,8 @@ def cmd(self): xdist_string = '--tx ssh=ubuntu@{}//python="source /edx/app/edxapp/edxapp_env; ' \ 'python"//chdir="/edx/app/edxapp/edx-platform"'.format(ip) cmd.append(xdist_string) - already_synced_dirs = set() - for test_path in self.test_id.split(): - test_root_dir = test_path.split('/')[0] - if test_root_dir not in already_synced_dirs: - cmd.append('--rsyncdir {}'.format(test_root_dir)) - already_synced_dirs.add(test_root_dir) + for rsync_dir in Env.rsync_dirs(): + cmd.append('--rsyncdir {}'.format(rsync_dir)) if self.eval_attr: cmd.append("-a '{}'".format(self.eval_attr)) @@ -277,7 +286,12 @@ def _under_coverage_cmd(self, cmd): unaltered otherwise. """ if self.run_under_coverage: - cmd.append('--cov') + if self.xdist_ip_addresses: + for module in Env.covered_modules(): + cmd.append('--cov') + cmd.append(module) + else: + cmd.append('--cov') if self.append_coverage: cmd.append('--cov-append') cmd.append('--cov-report=')