Merge lp://staging/~nuclearbob/autopilot/bug1210260 into lp://staging/autopilot

Proposed by Thomi Richards
Status: Merged
Approved by: Thomi Richards
Approved revision: 466
Merged at revision: 455
Proposed branch: lp://staging/~nuclearbob/autopilot/bug1210260
Merge into: lp://staging/autopilot
Diff against target: 388 lines (+292/-20)
2 files modified
autopilot/introspection/dbus.py (+95/-20)
autopilot/tests/unit/test_introspection_features.py (+197/-0)
To merge this branch: bzr merge lp://staging/~nuclearbob/autopilot/bug1210260
Reviewer Review Type Date Requested Status
Thomi Richards (community) Needs Fixing
PS Jenkins bot continuous-integration Needs Fixing
Max Brustkern (community) Needs Resubmitting
Review via email: mp+208260@code.staging.launchpad.net

Commit message

Autopilot emulators can declare a validate_dbus_object class method that takes a dbus path and state as arguments. (LP:1210260)

Description of the change

Autopilot emulators can declare a validate_dbus_object class method that takes a dbus path and state as arguments. If this function returns True, the emulator will be used. If multiple emulators return True, a ValueError will be raised. The default function preserves the current behavior by matching the dbus name to the class name.

To post a comment you must log in.
Revision history for this message
Thomi Richards (thomir-deactivatedaccount) wrote :

Resubmitted to get a better diff, since trunk has moved on since the MP was created.

Revision history for this message
Thomi Richards (thomir-deactivatedaccount) wrote :
Download full text (7.4 KiB)

Hi Max,

I assume you want a review on this, since you created this MP :)

Here goes:

12 + class_type = _select_emulator(_object_registry[self._id], path, state)

let's not call this '_select_emulator'. How about '_get_proxy_object_class'. While we're changing this line, let's improve the 'class_type' variable name - it's not a class type, it's a class. (the class type would be the metaclass). We can't use 'class' since it's a reserved keyword, so how about calling it 'class_object'?

22 + p<F7>7ath, state = dbus_tuple

ummm... O.0

31 + @classmethod
32 + def validate_dbus_object(cls, path, state):
33 + name = get_classname_from_path(path)
34 + return cls.__name__ == name

This needs a docstring. The docstring needs to describe:
 - What this method is for.
 - How users can override it.
 - What the default implementation does.

43 +def _select_emulator(objects, path, state):

Please change the parameter 'objects' to have a mode descriptive name. 'proxy_class_dictionary' or similar. 'objects' makes it sound like a list or tuple, when it's not.

44 + class_type = None

Same comment as above. I realise that this is all my fault :)

45 + for item_name in objects:
46 + item = objects[item_name]

Two things here. First, let's change thos evariable names to be a bit more informative. Second, use the dictionaries 'items' method if you want to get both keys and values (for k,v in my_dict.items():). However, in this case you only care about the values, so this should work:

for proxy_class in objects.values():

(note: I haven't changed the 'items' name from the previous point. I'm sure you can cope :))

On a further note, this entire function can be written in two lines:

possible_classes = [ c for c in objects.values() if c.validate_dbus_object(path, state) ]
if len(possible_classes) > 1:
    raise ValueError(...)
return possible_classes[0] if possible_classes else None

On a final note for this function, your exception needs more information. If we have more than one proxy class in this situation, we need to be able to debug that. A nice error message should include the classes that are applicable... so perhaps something like:

raise ValueError(
  "More than one custom proxy class matches this state. Possible classes are %s. State is %s. Path is %s" % (
    ','.join([repr(c) for c in possible_classes]),
    repr(state),
    path
  )
)

Also note that the error message really shouldn't use the word 'emulator' in it :)

63 +

In general, please avoid adding whitespace to diffs unless you have to.

109 + def test_class_has_validation_method(self):
110 + self.assertThat(callable(self.Rectangle.validate_dbus_object),
111 + Equals(True))

I don't think it's a good idea to assign 'Rectangle' to the class instance. Instead, nest them in the test class. Also, 'Rectangle' and 'Circle' names don't tell us anything about the classes, so let's use better names. Something like this should work:

class MakeIntrospectionObjectTests(TestCase):

  class DefaultSelector(CustomEmulatorBase):
    pass

  class AlwaysSelected(CustomEmulatorBase):
    @classmethod
    def validate_dbus_object(...): return True

  class NeverSelected(CustomEmulatorBase):
    @classmethod
...

Read more...

review: Needs Fixing
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Thomi Richards (thomir-deactivatedaccount) wrote :

Please set this back to 'needs review' when you'd like another review.

Thanks

Revision history for this message
Max Brustkern (nuclearbob) wrote :

Should be cleaned up now per Thomi's review.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Thomi Richards (thomir-deactivatedaccount) wrote :
Download full text (6.7 KiB)

Hi,

This is looking much better, thanks for your work. There are still a few issues though.

10 + :rtype: instance of appropriate CustomProxyObject class

You seem to like using the :rtype: field. In general, we haven't used this before in autopilot, so unless there's a good reason to, I'd prefer to encode this information in the :returns: field. For example, this:

9 + :returns: introspection object
10 + :rtype: instance of appropriate CustomProxyObject class

...could more simply be written as:

:returns: A proxy object that derives from DBusIntrospectionObject

...or something similar. If there's a good reason to use :rtype: that I've missed, please let me know.

51 + :type path: string

Same here. This:

50 + :param path: The dbus path of the object to check
51 + :type path: string

should just be:

:param path: A string containing the path of the object to check.

Finally, in general we don't insist on docstrings for private methods / functions, unless their intent is hard to divine from the function / class name (however, if tht's the case then the name probably needs to be changed). I'm not saying *don't* document them, just htat you don't need to go to so much effort to document them. Often a single line docstring will suffice.

Now, on to the tests:

    @patch('autopilot.introspection.dbus._try_custom_proxy_classes')
    @patch('autopilot.introspection.dbus._get_default_proxy_class')
    def test_get_proxy_object_class_from_list(self, gdpc, tcpc):
        """_get_proxy_object_class should return the value of
        _try_custom_proxy_classes if there is one."""
        gdpc.side_effect = Exception(
            'Called _get_default_proxy_class when _try_custom_proxy_classes '
            'returned a class.')
        retval = 'return'
        tcpc.return_value = retval
        class_dict = {'DefaultSelector': self.DefaultSelector}
        default = self.DefaultSelector
        path = '/path/to/DefaultSelector'
        state = {}
        gpoc_return = _get_proxy_object_class(class_dict,
                                              default,
                                              path,
                                              state)
        self.assertThat(gpoc_return, Equals(retval))
        tcpc.assert_called_once_with(class_dict, path, state)

A few things:

First, don't use the side_effects attribute on the mock to indicate a failure. First, it will produce an error, not a failure, and second, it's not particularly expressive.

Second, when you just want some placeholder text, it's better to use 'self.getUniqueString()', since it doesn't add a literal to the test that distracts from the test intent.

Third, since this is a unit test, this should fail in exactly one way. We often ignore that rule, but in this case, obeying it helps make this test a lot clearer. The intent of this test is that the '_get_proxy_object_class' should call '_try_custom_proxy_classes' first, not not call anything else if it succeeds. We can pair down the test until it's just testing that:

Here's how I'd do it:

    @patch('autopilot.introspection.dbus._try_custom_proxy_classes')
    @patch('autopilot.introspection.dbus._get_default_proxy_cl...

Read more...

review: Needs Fixing
Revision history for this message
Max Brustkern (nuclearbob) wrote :

More updates that should address the latest comments, I think.

review: Needs Resubmitting
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Thomi Richards (thomir-deactivatedaccount) wrote :

Hi,

This looks great - with one teensy exception - please change these:

 :raises: ValueError if more than one class in the dict matches

to be:

 :raises ValueError: if more than one class in the dict matches

i.e.- as we discussed in the PEP257 thread.

After that's done, I'm happy to merge it.

Cheers

Revision history for this message
Max Brustkern (nuclearbob) wrote :

Should be taken care of now.

review: Needs Resubmitting
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
466. By Max Brustkern

Merged trunk

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Thomi Richards (thomir-deactivatedaccount) wrote :

LGTM

review: Needs Fixing

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
The diff is not available at this time. You can reload the page or download it.

Subscribers

People subscribed via source and target branches