Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cache integration support #16

Merged
merged 2 commits into from
Jan 9, 2017
Merged

Add cache integration support #16

merged 2 commits into from
Jan 9, 2017

Conversation

goodboy
Copy link
Owner

@goodboy goodboy commented Jan 6, 2017

This adds support for stashing TestSets in the pytest cache between
runs. By default the lastfailed set is always inserted into the
shell namespace even when the user didn't pass --lf.

You can use:

  • cache <name> as <cache key> # create an entry
  • cache del <cache key> # delete and entry
  • cache # print a summary

Resolves #15

@goodboy goodboy self-assigned this Jan 6, 2017
@goodboy
Copy link
Owner Author

goodboy commented Jan 6, 2017

Ping for review @vodik @moises-silva @dkerr

Copy link
Contributor

@vodik vodik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you might want to tighten your line parsing, but otherwise looks reasonable.


def get_cache_dict(self, path=None):
if path is None:
path = '/'.join(['pytest-interactive', 'cache'])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where does this actually end up on the filesystem?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<pytest run dir>/.cache/v/pytest-interactive/cache

pytest stores lastfailed @ <pytest run dir>/.cache/v/lastfailed

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, glad they're following xdg

# update the local shell's ns
if testset:
self.shell.user_ns[cache_key] = testset
return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if someone inputs 'cache foo as '?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See below.

self.shell.user_ns.pop(cache_key)
self.tr.write(
"Deleted cache entry for '{}'\n".format(cache_key))
return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or something like 'cache del as ' actually?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hehe bad stuff.
So good point we need to validate names.
Any suggestion on how to do that?

Copy link
Contributor

@vodik vodik Jan 6, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tokenize the string first, consume the tokens one by one.

tokens = line.split()  # or use an alternative that respects quoting if you're feeling fancy
assert tokens[0] == 'cache'

if tokens[1] == 'del':
    assert len(tokens) == 3  # cache del foo
    name = tokens[2]
    # ...
else:
    assert len(tokens) == 4  # cache foo as ...
    assert token[2] == 'as'
    name = tokens[1]
    # ...

Which will also more correctly handle the case of putting too many whitespaces. Since you don't strip after you partition, i wonder how the following inputs would be handled:

  • cache foo as bar <- two spaces between foo and as, github is just cleaning it up for me ☹️
  • cache some random string as there's more to come
  • cache as as as

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback. Yes more stringent parsing is needed.
This seems like a good spot to bring in hypothesis when I finally get to #8...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also suggest requiring cache add and cache del, so you can correctly deal with a cache called del: cache del del... and cache add del...

Copy link
Owner Author

@goodboy goodboy Jan 6, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vodik so have add along side blah as foo?
I have to say I like the as/del as ptk gives you highlighting (yes i know it's hacky).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Take a step back and spec out how you want to parse the command:

operation = "add" | "del"
identifier = [A-Za-z0-9]
cmd = "cache" operation identifier ["as" identifier]

something like that, and unambigious. I'm not sure if you require "as", but if you don't, for example, what is the actual error here: 'cache del as'? Incorrect name? or missing identifier next to the as?

@goodboy
Copy link
Owner Author

goodboy commented Jan 6, 2017

Oh and obviously I should write some docs...

@goodboy
Copy link
Owner Author

goodboy commented Jan 9, 2017

@vodik fixed this up to simply use the add/del subcommand syntax.
I also added docs and think it's ready to land.

Let me know if there's anything I missed ;)

Copy link
Contributor

@vodik vodik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better, but could still use some work.

"Deleted cache entry for '{}'\n".format(name))
return

elif first == 'add':
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't match your documentation:

'0' selected >>> cache tt.test_setB.test_modes setb

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hah! thanks.

self.shell.user_ns[target] = testset
return

self.tt.err("{} is invalid".format(line))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not a bad idea to print either/both a usage line or a more specific error, if you can.


if first == 'del':
# delete an entry
name = tokens[1]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So no error if length is greater than 3?

@goodboy goodboy force-pushed the cache_support branch 2 times, most recently from 8111986 to 659f6ab Compare January 9, 2017 17:42
Copy link
Contributor

@vodik vodik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, while I think you should spend a bit more effort on tightening up the error messages, this looks functional.

@goodboy
Copy link
Owner Author

goodboy commented Jan 9, 2017

@vodik what more would you like with error msgs?
Right now as far as my manual testing has gone all the exceptions are appropriate.

@vodik
Copy link
Contributor

vodik commented Jan 9, 2017

Looks good to me.

Actually, one last thing...

Copy link
Contributor

@vodik vodik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, looks fine to me now. Just two small comments, but I guess its a bit of scope creep - I could see the identifier validity problem as something more general

"Created cache entry for '{}'\n".format(name))
return

self.tt.err("{} is invalid. See %cache? for usage.".format(line))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heh, for all the hard times I give you for putting unnecessary quotes around your format strings, here's actually a good place to put some in.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lolz

# create a new entry
name, target = tokens[1:]
testset = self.ns_eval(name)
self.tt.set_cache_items(target, testset)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, would you maybe want to validate that target is a valid python keyword? You know, starts with a letter and doesn't contain invalid characters like dashes?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep good call.

Tyler Goodlet added 2 commits January 9, 2017 16:57
This adds support for stashing `TestSet`s in the pytest cache between
runs. By default the `lastfailed` set is always inserted into the
shell namespace even when the user didn't pass `--lf`.

You can use:
- cache   # print a summary
- cache add <name> <cache key>  # create an entry
- cache del <name>  # delete and entry

Resolves #15
@goodboy
Copy link
Owner Author

goodboy commented Jan 9, 2017

@vodik good enough?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants