def test_path_prepend(self): d = { 'PATH': self.native_path('foo:bar:baz'), } env_var(d, 'PATH').prepend('apple') self.assertEqual(['apple', 'foo', 'bar', 'baz'], env_var(d, 'PATH').path)
def test_path(self): d = { 'PATH': self.native_path('foo:bar:baz'), } self.assertEqual(self.native_path('foo:bar:baz'), self.native_path(env_var(d, 'PATH').value)) self.assertEqual(['foo', 'bar', 'baz'], env_var(d, 'PATH').path)
def test_path_duplicates(self): d = { 'PATH': self.native_path('foo:bar:baz:foo'), } self.assertEqual(self.native_path('foo:bar:baz:foo'), env_var(d, 'PATH').value) self.assertEqual(['foo', 'bar', 'baz'], env_var(d, 'PATH').path)
def _create(clazz, root_dir, exe): clazz._log.log_method_d() venv_config = path.join(root_dir, 'pyvenv.cfg') if path.isfile(venv_config): return cmd = [ exe, '-m', 'venv', root_dir, ] env = os_env.make_clean_env() PATH = env_var(env, 'PATH') PATH.prepend(path.dirname(exe)) PYTHONPATH = env_var(env, 'PYTHONPATH') PYTHONPATH.unset() clazz._log.log_d('_create: env={}'.format(pprint.pformat(env))) clazz._log.log_d('_create: cmd={}'.format(' '.join(cmd))) with dir_cleanup(tempfile.gettempdir()) as ctx: rv = execute.execute(cmd, env=env, raise_error=False) if rv.exit_code != 0: raise python_error('failed to init virtual env: "{}" - {}'.format( ' '.join(cmd), rv.stderr)) clazz._ensure_versioned_python_exe(root_dir, exe)
def test_name(self): d = { 'FOO': '666', 'BAR': 'hello', 'PATH': self.native_path('foo:bar:baz'), } self.assertEqual('666', env_var(d, 'FOO').value) self.assertEqual(['666'], env_var(d, 'FOO').path) e = env_var(d, 'FOO') e.value = '1000' self.assertEqual('1000', env_var(d, 'FOO').value)
def transform_env(self, env): new_env = copy.deepcopy(env) for inst in self.instructions(env): if inst.action == action.SET: new_env[inst.key] = inst.value elif inst.action == action.UNSET: if inst.key in new_env: del new_env[inst.key] elif inst.action == action.PATH_APPEND: v = env_var(new_env, inst.key) v.append(inst.value) elif inst.action == action.PATH_PREPEND: v = env_var(new_env, inst.key) v.prepend(inst.value) elif inst.action == action.PATH_REMOVE: v = env_var(new_env, inst.key) v.remove(inst.value) return new_env
def env(self): 'Make a clean environment for python or pip' clean_env = os_env.make_clean_env() env_var(clean_env, 'PYTHONUSERBASE').value = self.root_dir env_var(clean_env, 'PYTHONPATH').path = self.PYTHONPATH env_var(clean_env, 'PATH').prepend(self.PATH) env_var(clean_env, 'HOME').value = self._fake_home_dir env_var(clean_env, 'TMPDIR').value = self._fake_tmp_dir env_var(clean_env, 'TMP').value = self._fake_tmp_dir env_var(clean_env, 'TEMP').value = self._fake_tmp_dir clean_env.update(self._extra_env) return clean_env
def call_program(self, args, **kargs): 'Call a program with the right environment' command_line.check_args_type(args) kargs = copy.deepcopy(kargs) self._log.log_method_d() self._log.log_d('call_program: args={}'.format(args)) parsed_args = command_line.parse_args(args) self._log.log_d('call_program: parsed_args={}'.format(parsed_args)) env = os_env.clone_current_env() env.update(self.env) PATH = env_var(env, 'PATH') PYTHONPATH = env_var(env, 'PYTHONPATH') if 'env' in kargs: kargs_env = kargs['env'] del kargs['env'] if 'PATH' in kargs_env: PATH.append(kargs_env['PATH']) del kargs_env['PATH'] if 'PYTHONPATH' in kargs_env: PYTHONPATH.append(kargs_env['PYTHONPATH']) del kargs_env['PYTHONPATH'] env.update(kargs_env) PATH.prepend(self.PATH) PYTHONPATH.prepend(self.PYTHONPATH) kargs['env'] = env self._log.log_d('call_program: env={}'.format(env)) kargs['shell'] = True kargs['check_python_script'] = False for key, value in sorted(env.items()): self._log.log_d('call_program({}): ENV: {}={}'.format(args[0], key, value)) return execute.execute(parsed_args, **kargs)
def test_path_remove(self): d = { 'PATH': self.native_path('foo:bar:baz'), } env_var(d, 'PATH').remove('bar') self.assertEqual(['foo', 'baz'], env_var(d, 'PATH').path)
def update_environment(self, env, variables): for config in self._env_dependencies_configs: substituted = config.substitute(variables) env_var(env, 'PATH').append(substituted.data.unixpath) env_var(env, 'PYTHONPATH').append(substituted.data.pythonpath)
def main(): DEBUG = os.environ.get('DEBUG', False) import bes vcli = version_cli(bes) parser = argparse.ArgumentParser() parser.add_argument('files', action='store', nargs='*', help='Files or directories to rename') vcli.version_add_arguments(parser) parser.add_argument('--dry-run', '-n', action='store_true', default=False, help='Only print what files will get tests [ False ]') parser.add_argument( '--timing', '-t', action='store_true', default=False, help='Show the amount of time it takes to run tests [ False ]') parser.add_argument('--verbose', '-v', action='store_true', default=False, help='Verbose debug output [ False ]') parser.add_argument('--stop', '-s', action='store_true', default=False, help='Stop right after the first failure. [ False ]') parser.add_argument( '--randomize', action='store_true', default=False, help='Randomize the order in which unit tests run. [ False ]') parser.add_argument( '--python', action='append', default=[], help= 'Python executable) to use. Multiple flags can be used for running with mutiple times with different python versions [ python ]' ) parser.add_argument('--page', '-p', action='store_true', default=False, help='Page output with $PAGER [ False ]') parser.add_argument( '--profile', action='store', default=None, help= 'Profile the code with cProfile and store the output in the given argument [ None ]' ) parser.add_argument( '--coverage', action='store', default=None, help= 'Run coverage on the code and store the output in the given argument [ None ]' ) parser.add_argument('--pager', action='store', default=os.environ.get('PAGER', 'more'), help='Pager to use when paging [ %s ]' % (os.environ.get('PAGER', 'more'))) parser.add_argument('--iterations', '-i', action='store', default=1, type=int, help='Python executable to use [ python ]') parser.add_argument( '--git', '-g', action='store_true', default=False, help='Use git status to figure out what has changed to test [ False ]') parser.add_argument( '--commit', '-c', action='store', type=str, default=None, help='Test only the files affected by the given git commit [ None ]') parser.add_argument('--pre-commit', action='store_true', default=False, help='Run pre commit checks [ False ]') parser.add_argument('--print-tests', action='store_true', default=False, help='Print the list of unit tests [ False ]') parser.add_argument('--print-python', action='store_true', default=False, help='Print the detected python executable [ False ]') parser.add_argument('--print-files', action='store_true', default=False, help='Print the list of unit files [ False ]') parser.add_argument( '--egg', action='store_true', default=False, help= 'Make an egg of the package and run the tests against that instead the live files. [ False ]' ) parser.add_argument( '--save-egg', action='store_true', default=False, help='Save the egg in the current directory. [ False ]') parser.add_argument('--ignore', action='append', default=[], help='Patterns of filenames to ignore []') parser.add_argument( '--root-dir', action='store', default=None, help= 'The root directory for all your projets. By default its computed from your git struture. [ None ]' ) parser.add_argument('--dont-hack-env', action='store_true', default=False, help='Dont hack PATH and PYTHONPATH. [ False ]') parser.add_argument( '--compile-only', action='store_true', default=False, help='Just compile the files to verify syntax [ False ]') parser.add_argument( '--print-deps', action='store_true', default=False, help='Print python dependencies for test files [ False ]') parser.add_argument('--print-configs', action='store_true', default=False, help='Print testing configs found [ False ]') parser.add_argument('--print-root-dir', action='store_true', default=False, help='Print the root dir [ False ]') parser.add_argument('--print-path', action='store_true', default=False, help='Print sys.path [ False ]') parser.add_argument( '--file-ignore-file', action='append', default=[], help= 'List of file ignore files. [ .bes_test_ignore .bes_test_internal_ignore ]' ) parser.add_argument('--env', action='append', default=[], help='Environment variables to set [ None ]') parser.add_argument('--no-env-deps', action='store_true', default=False, help='Dont use env deps. [ False ]') parser.add_argument( '--temp-dir', action='store', default=None, help= 'The directory to use for tmp files overriding the system default. [ None ]' ) parser.add_argument( '--keep-side-effects', action='store_true', default=DEBUG, help='Dont delete side effects - for debugging. [ False ]') parser.add_argument( '--ignore-side-effects', action='store_true', default=DEBUG, help='Dont delete side effects - for debugging. [ False ]') found_git_exe = git_exe.find_git_exe() if not found_git_exe: printer.writeln_name( 'ERROR: No git found. Git is needed to run bes_test.') return 1 for g in parser._action_groups: g._group_actions.sort(key=lambda x: x.dest) args = parser.parse_args() args.python = _resolve_python_exe_list(args.python) if not args.python: python_exe = python.find_python_exe() if python_exe: args.python = [python_exe] if not args.python: printer.writeln_name( 'ERROR: No python found. Python is needed to run bes_test.') return 1 _LOG.log_d('using python={}'.format(args.python)) if args.git and args.commit: printer.writeln_name( 'ERROR: Only one of --git or --commit can be given.') return 1 if args.temp_dir: file_util.mkdir(args.temp_dir) tempfile.tempdir = args.temp_dir if DEBUG: args.verbose = True cwd = os.getcwd() if args.version: vcli.version_print_version() return 0 args.env = _parse_args_env(args.env) if not args.files: args.files = [cwd] if not args.file_ignore_file: args.file_ignore_file = [ '.bes_test_ignore', '.bes_test_internal_ignore' ] if args.commit: if args.commit in ['HEAD', 'last']: args.commit = git.last_commit_hash('.') ar = argument_resolver(cwd, args.files, root_dir=args.root_dir, file_ignore_filename=args.file_ignore_file, check_git=args.git, git_commit=args.commit, use_env_deps=not args.no_env_deps) ar.num_iterations = args.iterations ar.randomize = args.randomize ar.ignore_with_patterns(args.ignore) if args.compile_only: total_files = len(ar.all_files) for i, f in enumerate(ar.all_files): tmp = temp_file.make_temp_file() filename_count_blurb = ' ' + _make_count_blurb(i + 1, total_files) short_filename = file_util.remove_head(f, cwd) blurb = '%7s:%s %s ' % ('compile', filename_count_blurb, short_filename) printer.writeln_name(blurb) py_compile.compile(f, cfile=tmp, doraise=True) return 0 if not ar.test_descriptions: return 1 if args.print_python: for python_exe in args.python: print(python_exe) return 0 if args.print_path: for p in sys.path: print(p) return 0 if args.print_configs: ar.print_configs() return 0 if args.print_root_dir: print(ar.root_dir) return 0 if args.print_files: ar.print_files() return 0 if args.print_tests: ar.print_tests() return 0 if args.print_deps or args.pre_commit and not ar.supports_test_dependency_files( ): printer.writeln_name( 'ERROR: Cannot figure out dependencies. snakefood missing.') return 1 if args.print_deps: dep_files = ar.test_dependency_files() for filename in sorted(dep_files.keys()): print(filename) for dep_file in dep_files[filename]: print(' %s' % (dep_file.filename)) return 0 # Read ~/.bes_test/bes_test.config (or use a default config) bes_test_config = _read_config_file() keep_patterns = bes_test_config.get_value_string_list( 'environment', 'keep_patterns') # Start with a clean environment so unit testing can be deterministic and not subject # to whatever the user happened to have exported. PYTHONPATH and PATH for dependencies # are set below by iterating the configs keep_keys = bes_test_config.get_value_string_list('environment', 'keep_keys') if args.dont_hack_env: keep_keys.extend(['PATH', 'PYTHONPATH']) keep_keys.extend(['TMPDIR', 'TEMP', 'TMP']) env = os_env.make_clean_env( keep_keys=keep_keys, keep_func=lambda key: _env_var_should_keep(key, keep_patterns)) env_var(env, 'PATH').prepend(path.dirname(found_git_exe)) for python_exe in args.python: env_var(env, 'PATH').prepend(path.dirname(python_exe)) env['PYTHONDONTWRITEBYTECODE'] = 'x' variables = { 'rebuild_dir': path.expanduser('~/.rebuild'), 'system': host.SYSTEM, } if not args.dont_hack_env: for var in ar.env_dependencies_variables(): ov = os_env_var(var) if ov.is_set: value = ov.value else: value = '' variables[var] = value ar.update_environment(env, variables) # Update env with whatever was given in --env env.update(args.env) # Use a custom TMP dir so that we can catch temporary side effects and flag them tmp_tmp = temp_file.make_temp_dir(prefix='bes_test_', suffix='.tmp.tmp.dir', delete=False) env.update({ 'TMPDIR': tmp_tmp, 'TEMP': tmp_tmp, 'TMP': tmp_tmp, }) side_effects = {} num_passed = 0 num_failed = 0 num_executed = 0 num_tests = len(ar.test_descriptions) failed_tests = [] # Remove current dir from sys.path to avoid side effects if cwd in sys.path: sys.path.remove(cwd) if args.egg: pythonpath = env_var(env, 'PYTHONPATH') pythonpath.remove(cwd) for config in ar.env_dependencies_configs: setup_dot_py = path.join(config.root_dir, 'setup.py') if not path.isfile(setup_dot_py): raise RuntimeError('No setup.py found in %s to make the egg.' % (cwd)) egg_zip = egg.make(config.root_dir, 'master', setup_dot_py, untracked=False) pythonpath.prepend(egg_zip) printer.writeln_name('using tmp egg: %s' % (egg_zip)) if args.save_egg: file_util.copy(egg_zip, path.join(cwd, path.basename(egg_zip))) if args.pre_commit: missing_from_git = [] for filename, dep_files in ar.test_dependency_files().items(): for dep_file in dep_files: if dep_file.config and not dep_file.git_tracked: missing_from_git.append(dep_file.filename) if missing_from_git: for f in missing_from_git: printer.writeln_name('PRE_COMMIT: missing from git: %s' % (path.relpath(f))) return 1 return 0 ar.cleanup_python_compiled_files() # Do all our work with a temporary working directory to be able to check for side effects tmp_cwd = temp_file.make_temp_dir(prefix='bes_test_', suffix='.tmp.cwd.dir', delete=False) tmp_home = temp_file.make_temp_dir(prefix='bes_test_', suffix='.tmp.home.dir', delete=False) os.environ['HOME'] = tmp_home os.chdir(tmp_cwd) # Use what the OS thinks the path is (to deal with symlinks and virtual tmpfs things) tmp_cwd = os.getcwd() if not args.dry_run and args.page: printer.OUTPUT = tempfile.NamedTemporaryFile(prefix='bes_test', delete=True, mode='w') total_tests = _count_tests(ar.inspect_map, ar.test_descriptions) total_files = len(ar.test_descriptions) total_num_tests = 0 if args.profile: args.profile = path.abspath(args.profile) if not _check_program('cprofilev'): return 1 if args.coverage: args.coverage = path.abspath(args.coverage) coverage_exe = _check_program('coverage') if not coverage_exe: return 1 args.python = [coverage_exe] if args.profile and args.coverage: printer.writeln_name( 'ERROR: --profile and --coverage are mutually exclusive.') return 1 options = test_options(args.dry_run, args.verbose, args.stop, args.timing, args.profile, args.coverage, args.python, args.temp_dir, tmp_home) timings = {} total_time_start = time.time() stopped = False for i, test_desc in enumerate(ar.test_descriptions): file_info = test_desc.file_info filename = file_info.filename if not filename in timings: timings[filename] = [] for python_exe in args.python: result = _test_execute(python_exe, ar.inspect_map, filename, test_desc.tests, options, i + 1, total_files, cwd, env) _collect_side_effects(side_effects, filename, tmp_home, 'home', args.keep_side_effects) _collect_side_effects(side_effects, filename, tmp_tmp, 'tmp', args.keep_side_effects) _collect_side_effects(side_effects, filename, os.getcwd(), 'cwd', args.keep_side_effects) timings[filename].append(result.elapsed_time) total_num_tests += result.num_tests_run num_executed += 1 if result.success: num_passed += 1 else: num_failed += 1 failed_tests.append((python_exe, filename, result)) if args.stop and not result.success: stopped = True if stopped: break total_elapsed_time = 1000 * (time.time() - total_time_start) if args.dry_run: return 0 num_skipped = num_tests - num_executed summary_parts = [] if total_num_tests == total_tests: function_summary = '(%d %s)' % (total_tests, _make_test_string(total_tests)) else: function_summary = '(%d of %d %s)' % (total_num_tests, total_tests, _make_test_string(total_tests)) if num_failed > 0: summary_parts.append('%d of %d fixtures FAILED' % (num_failed, num_tests)) summary_parts.append('%d of %d passed %s' % (num_passed, num_tests, function_summary)) if num_skipped > 0: summary_parts.append('%d of %d skipped' % (num_skipped, num_tests)) summary = '; '.join(summary_parts) printer.writeln_name('%s' % (summary)) if failed_tests: longest_python_exe = max( [len(path.basename(p)) for p in options.interpreters]) for python_exe, filename, result in failed_tests: if len(options.interpreters) > 1: python_exe_blurb = path.basename(python_exe).rjust( longest_python_exe) else: python_exe_blurb = '' error_status = unit_test_output.error_status(result.output) for error in error_status.errors: printer.writeln_name('%5s: %s %s :%s.%s' % (error.error_type, python_exe_blurb, file_util.remove_head(filename, cwd), error.fixture, error.function)) if num_failed > 0: rv = 1 else: rv = 0 if args.timing: filenames = sorted(timings.keys()) num_filenames = len(filenames) for i, filename in zip(range(0, num_filenames), filenames): short_filename = file_util.remove_head(filename, cwd) all_timings = timings[filename] num_timings = len(all_timings) avg_ms = _timing_average(all_timings) * 1000.0 if num_timings > 1: run_blurb = '(average of %d runs)' % (num_timings) else: run_blurb = '' if num_filenames > 1: count_blurb = '[%s of %s] ' % (i + 1, num_filenames) else: count_blurb = '' printer.writeln_name( 'timing: %s%s - %2.2f ms %s' % (count_blurb, short_filename, avg_ms, run_blurb)) if total_elapsed_time >= 1000.0: printer.writeln_name('total time: %2.2f s' % (total_elapsed_time / 1000.0)) else: printer.writeln_name('total time: %2.2f ms' % (total_elapsed_time)) if args.page: subprocess.call([args.pager, printer.OUTPUT.name]) current_cwd = os.getcwd() if current_cwd != tmp_cwd: rv = 1 printer.writeln_name( 'SIDE EFFECT: working directory was changed from %s to %s' % (tmp_cwd, current_cwd)) if not args.ignore_side_effects: for test, items in sorted(side_effects.items()): for item in items: rv = 1 filename = item.filename print('SIDE EFFECT [{}] {} {}'.format( item.label, test.replace(cwd + os.sep, ''), filename)) os.chdir('/tmp') if not args.keep_side_effects: file_util.remove(tmp_cwd) file_util.remove(tmp_home) file_util.remove(tmp_tmp) return rv