def core_help_option_prints_core_help(self): # TODO: change dynamically based on parser contents? # e.g. no core args == no [--core-opts], # no tasks == no task stuff? # NOTE: test will trigger default pty size of 80x24, so the below # string is formatted appropriately. # TODO: add more unit-y tests for specific behaviors: # * fill terminal w/ columns + spacing # * line-wrap help text in its own column expected = """ Usage: inv[oke] [--core-opts] task1 [--task1-opts] ... taskN [--taskN-opts] Core options: --no-dedupe Disable task deduplication -c STRING, --collection=STRING Specify collection name to load. May be given >1 time. -h, --help Show this help message and exit. -l, --list List available tasks. -r STRING, --root=STRING Change root directory used for finding task modules. -V, --version Show version and exit """.lstrip() r1 = run("inv -h", hide='out') r2 = run("inv --help", hide='out') eq_(r1.stdout, expected) eq_(r2.stdout, expected)
def ensure_deepcopy_works(self): lex = Lexicon() lex['foo'] = 'bar' eq_(lex.foo, 'bar') lex2 = copy.deepcopy(lex) lex2.foo = 'biz' assert lex2.foo != lex.foo
def globbed_shortflags_with_multipass_parsing(self): "mytask -cb and -bc" for args in ('-bc', '-cb'): _, _, r = parse(['mytask4', args], self.c) a = r[0].args eq_(a.clean.value, True) eq_(a.browse.value, True)
def positionalness_and_optionalness_stick_together(self): # TODO: but do these even make sense on the same argument? For now, # best to have a nonsensical test than a missing one... eq_( repr(Argument('name', optional=True, positional=True)), "<Argument: name *?>", )
def core_help_option_prints_core_help(self): # TODO: change dynamically based on parser contents? # e.g. no core args == no [--core-opts], # no tasks == no task stuff? # NOTE: test will trigger default pty size of 80x24, so the below # string is formatted appropriately. # TODO: add more unit-y tests for specific behaviors: # * fill terminal w/ columns + spacing # * line-wrap help text in its own column expected = """ Usage: inv[oke] [--core-opts] task1 [--task1-opts] ... taskN [--taskN-opts] Core options: --no-dedupe Disable task deduplication. -c STRING, --collection=STRING Specify collection name to load. May be given >1 time. -e, --echo Echo executed commands before running. -h [STRING], --help[=STRING] Show core or per-task help and exit. -H STRING, --hide=STRING Set default value of run()'s 'hide' kwarg. -l, --list List available tasks. -p, --pty Use a pty when executing shell commands. -r STRING, --root=STRING Change root directory used for finding task modules. -V, --version Show version and exit. -w, --warn-only Warn, instead of failing, when shell commands fail. """.lstrip() r1 = run("inv -h", hide='out') r2 = run("inv --help", hide='out') eq_(r1.stdout, expected) eq_(r2.stdout, expected)
def no_deduping(self): expected = """ foo foo bar """.lstrip() eq_(run("invoke -c integration --no-dedupe foo bar").stdout, expected)
def multiple_short_flags_adjacent(self): "mytask -bv (and inverse)" for args in ('-bv', '-vb'): r = self._parse("mytask %s" % args) a = r[0].args eq_(a.b.value, True) eq_(a.v.value, True)
def subcollections_sorted_in_depth_order(self): expected = self._listing( 'toplevel', 'a.subtask', 'a.nother.subtask', ) eq_(run("invoke -c deeper_ns_list --list").stdout, expected)
def default_tasks(self): # sub-ns default task display as "real.name (collection name)" expected = self._listing( 'top_level (othertop)', 'sub.sub_task (sub, sub.othersub)', ) eq_(run("invoke -c explicit_root --list").stdout, expected)
def always_includes_initial_context_if_one_was_given(self): # Even if no core/initial flags were seen t1 = Context('t1') init = Context() result = Parser((t1,), initial=init).parse_argv(['t1']) eq_(result[0].name, None) eq_(result[1].name, 't1')
def submodule_names_are_stripped_to_last_chunk(self): with support_path(): from package import module c = Collection.from_module(module) eq_(module.__name__, 'package.module') eq_(c.name, 'module') assert 'mytask' in c # Sanity
def release_line_bugfix_specifier(self): b50 = _issue('bug', '50') b42 = _issue('bug', '42', line='1.1') f25 = _issue('feature', '25') b35 = _issue('bug', '35') b34 = _issue('bug', '34') f22 = _issue('feature', '22') b20 = _issue('bug', '20') c = _changelog2dict(_releases( '1.2.1', '1.1.2', '1.0.3', b50, b42, '1.2.0', '1.1.1', '1.0.2', f25, b35, b34, '1.1.0', '1.0.1', f22, b20 )) for rel, issues in ( ('1.0.1', [b20]), ('1.1.0', [f22]), ('1.0.2', [b34, b35]), ('1.1.1', [b34, b35]), ('1.2.0', [f25]), ('1.0.3', [b50]), # the crux - is not b50 + b42 ('1.1.2', [b50, b42]), ('1.2.1', [b50, b42]), ): eq_(set(c[rel]), set(issues))
def arguments_which_take_values_get_defaults_overridden_correctly(self): args = (Argument('arg', kind=str), Argument('arg2', kind=int)) c = Context('mytask', args=args) argv = ['mytask', '--arg', 'myval', '--arg2', '25'] result = Parser((c,)).parse_argv(argv) eq_(result[0].args['arg'].value, 'myval') eq_(result[0].args['arg2'].value, 25)
def replaced_stdin_objects_dont_explode(self, mock_sys): # Replace sys.stdin with an object lacking .fileno(), which # normally causes an AttributeError unless we are being careful. mock_sys.stdin = object() # Test. If bug is present, this will error. runner = Local(Context()) eq_(runner.should_use_pty(pty=True, fallback=True), False)
def searches_towards_root_of_filesystem(self): # Loaded while root is in same dir as .py directly = self.l.load('foo') # Loaded while root is multiple dirs deeper than the .py deep = os.path.join(support, 'ignoreme', 'ignoremetoo') indirectly = FSLoader(start=deep).load('foo') eq_(directly, indirectly)
def yaml_prevents_json_or_python(self): c = Config( system_prefix=join(CONFIGS_PATH, 'all-three', 'invoke')) ok_('json-only' not in c) ok_('python_only' not in c) ok_('yaml-only' in c) eq_(c.shared, 'yaml-value')
def return_code_in_result(self): """ Result has .return_code (and .exited) containing exit code int """ r = run(self.out, hide='both') eq_(r.return_code, 0) eq_(r.exited, 0)
def env_vars_override_project(self): os.environ['HOORAY'] = 'env' c = Config( project_home=join(CONFIGS_PATH, 'yaml'), ) c.load_shell_env() eq_(c.hooray, 'env')
def env_vars_override_systemwide(self): os.environ['HOORAY'] = 'env' c = Config( system_prefix=join(CONFIGS_PATH, 'yaml', 'invoke'), ) c.load_shell_env() eq_(c.hooray, 'env')
def non_spurious_OSErrors_bubble_up(self): try: self._run(_, pty=True) except ThreadException as e: e = e.exceptions[0] eq_(e.type, OSError) eq_(str(e.value), "wat")
def subkeys_get_merged_not_overwritten(self): # Ensures nested keys merge deeply instead of shallowly. defaults = {'foo': {'bar': 'baz'}} overrides = {'foo': {'notbar': 'notbaz'}} c = Config(defaults=defaults, overrides=overrides) eq_(c.foo.notbar, 'notbaz') eq_(c.foo.bar, 'baz')
def env_vars_override_user(self): os.environ['OUTER_INNER_HOORAY'] = 'env' c = Config( user_prefix=join(CONFIGS_PATH, 'yaml', 'invoke'), ) c.load_shell_env() eq_(c.outer.inner.hooray, 'env')
def unsets_aliases(self): ad = AliasDict() ad['realkey'] = 'value' ad.alias('myalias', to='realkey') eq_(ad['myalias'], 'value') ad.unalias('myalias') assert 'myalias' not in ad
def can_clone_into_a_subclass(self): orig = Call(self.task) class MyCall(Call): pass clone = orig.clone(into=MyCall) eq_(clone, orig) ok_(isinstance(clone, MyCall))
def deletion_is_recursive(self): ad = self._recursive_aliases() del ad['alias2'] assert 'realkey' not in ad ad['realkey'] = 'newvalue' assert 'alias1' in ad eq_(ad['alias1'], 'newvalue')
def ensure_deepcopy_works(self): ad = AttributeDict() ad['foo'] = 'bar' eq_(ad.foo, 'bar') ad2 = copy.deepcopy(ad) ad2.foo = 'biz' assert ad2.foo != ad.foo
def multiple_short_flags_adjacent(self): "mytask -bv (and inverse)" for args in ("-bv", "-vb"): r = self._parse("mytask {0}".format(args)) a = r[0].args eq_(a.b.value, True) eq_(a.v.value, True)
def comparison_looks_at_merged_config(self): c1 = Config(defaults={'foo': {'bar': 'biz'}}) # Empty defaults to suppress global_defaults c2 = Config(defaults={}, overrides={'foo': {'bar': 'biz'}}) ok_(c1 is not c2) ok_(c1._defaults != c2._defaults) eq_(c1, c2)
def simple_command(self): group = Group('localhost', '127.0.0.1') result = group.run('echo foo', hide=True) eq_( [x.stdout.strip() for x in result.values()], ['foo', 'foo'], )
def release_line_bugfix_specifier(self): b50 = b(50) b42 = b(42, spec='1.1+') f25 = f(25) b35 = b(35) b34 = b(34) f22 = f(22) b20 = b(20) c = changelog2dict(releases( '1.2.1', '1.1.2', '1.0.3', b50, b42, '1.2.0', '1.1.1', '1.0.2', f25, b35, b34, '1.1.0', '1.0.1', f22, b20 )) for rel, issues in ( ('1.0.1', [b20]), ('1.1.0', [f22]), ('1.0.2', [b34, b35]), ('1.1.1', [b34, b35]), ('1.2.0', [f25]), ('1.0.3', [b50]), # the crux - is not b50 + b42 ('1.1.2', [b50, b42]), ('1.2.1', [b50, b42]), ): eq_(set(c[rel]), set(issues))
def equals_sign_for_long_form_only(self): eq_(self.tasked.help_for('--myarg'), ("-m STRING, --myarg=STRING", ""))
def exposed_as_Lexicon(self): eq_(self.c.args.bar, self.c.args['bar'])
def repr_is_str(self): "__repr__ mirrors __str__" c = Context('foo') eq_(str(c), repr(c))
def args_show_as_repr(self): eq_(str(Context('bar', args=[Argument('arg1')])), "<Context 'bar': {'arg1': <Argument: arg1>}>")
def with_no_args_output_is_simple(self): eq_(str(Context('foo')), "<Context 'foo'>")
def _assert_order(self, name_tuples, expected_flag_order): ctx = Context(args=[Argument(names=x) for x in name_tuples]) return eq_(ctx.help_tuples(), [ctx.help_for(x) for x in expected_flag_order])
def shortflag_inputs_work_too(self): eq_(self.tasked.help_for('-m'), self.tasked.help_for('--myarg'))
def task_driven_no_helpstr(self): eq_(self.tasked.help_for('--myarg'), ("-m STRING, --myarg=STRING", ""))
def vanilla_with_helpstr(self): eq_(self.vanilla.help_for('--bar'), ("--bar=STRING", "bar the baz"))
def short_form_before_long_form(self): eq_(self.tasked.help_for('--myarg'), ("-m STRING, --myarg=STRING", ""))
def includes_arguments(self): eq_(len(self.new.args), 1) assert self.new.args['--boolean'] is not self.arg
def task_driven_with_helpstr(self): eq_(self.tasked.help_for('--otherarg'), ("-o STRING, --otherarg=STRING", "other help"))
def positional_arg_modifications_affect_args_copy(self): self.c.add_arg(name='hrm', positional=True) eq_(self.c.args['hrm'].value, self.c.positional_args[0].value) self.c.positional_args[0].value = 17 eq_(self.c.args['hrm'].value, self.c.positional_args[0].value)
def vanilla_no_helpstr(self): eq_(self.vanilla.help_for('--foo'), ("--foo=STRING", ""))
def positional_args_filled_in_order(self): self.c.add_arg(name='pos1', positional=True) eq_(self.c.positional_args[0].name, 'pos1') self.c.add_arg(name='abc', positional=True) eq_(self.c.positional_args[1].name, 'abc')
def returns_correct_copy(self): assert self.new is not self.orig eq_(self.new.name, 'mytask') assert 'othername' in self.new.aliases
def adds_positional_args_to_positional_args(self): self.c.add_arg(name='pos', positional=True) eq_(self.c.positional_args[0].name, 'pos')
def may_have_a_name(self): c = Context(name='taskname') eq_(c.name, 'taskname')
def warn_kwarg_allows_continuing_past_failures(self): eq_(run("false", warn=True).exited, 1)
def positional_args_empty_when_none_given(self): eq_(len(self.c.positional_args), 0)
def failed_attr_indicates_failure(self): eq_(_run().failed, False) eq_(_run(returns={'exited': 1}, warn=True).failed, True)
def _hide_both(self, val): run(self.both, hide=val) eq_(sys.stdall.getvalue(), "")
def stderr_attribute_contains_stderr(self): eq_(run(self.err, hide='both').stderr, 'bar\n')
def has_exception_attr(self): eq_(_run().exception, None)
def nonzero_return_code_for_failures(self): result = run("false", warn=True) eq_(result.exited, 1) result = run("goobypls", warn=True, hide='both') eq_(result.exited, 127)
def ok_attr_indicates_success(self): eq_(_run().ok, True) eq_(_run(returns={'exited': 1}, warn=True).ok, False)
def stdout_contains_both_streams_under_pty(self): r = run(self.both, hide='both', pty=True) eq_(r.stdout, 'foo\r\nbar\r\n')
def stdout_attribute_contains_stdout(self): eq_(run(self.out, hide='both').stdout, 'foo\n')
def when_echo_True_commands_echoed_in_bold(self): run("echo hi", echo=True) expected = "\033[1;37mecho hi\033[0m\nhi" eq_(sys.stdout.getvalue().strip(), expected)
def stderr_is_empty_under_pty(self): r = run(self.both, hide='both', pty=True) eq_(r.stderr, '')