Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write Python doctest that is OS independent regarding path separator

Is there a way to have a doctest with file paths as output that will succeed regardless of the OS it's run on?

For example, on Windows this will work:

r"""
>>> import foo
>>> relative_path = foo.getRelativePath()
>>> print relative_path 
'bar\\foobar'
"""
if __name__ == '__main__':
    from doctest import testmod
    print testmod()

But of course will fail on Linux and produce an error similar to:

Failed example:
    print relative_path 
Expected:
    'bar\\foobar'
Got:
    'bar/foobar'

How can I make the above work on any OS?

EDIT

I know I can do something like this instead:

>>> relative_path == os.path.join('bar', 'foobar')
True

But I'm wondering if there is a different and better way to do this.

like image 734
asherbret Avatar asked Aug 12 '14 07:08

asherbret


1 Answers

Clarification

Doctests are alluring because of their simplicity, but it's a misleading simplicity. You expect the test line to represent an expression that doctest will evaluate against the result of the last expression, but it's not; it's actually just doing a simple, basic string comparison.

#doctesttest.py
"""
>>> "test"
"test"

python -m doctest doctesttest.py

Gives

...
Expected:
    "test"
Got:
    'test'

Although - in pythonic terms - "test" == 'test', even "test" is 'test', str(""" 'test' """) does not match str(""" "test" """).

Armed with this awareness...

Solution

The following will fail on all systems:

def unique_paths(path_list):
    """ Returns a list of normalized-unique paths based on path_list
    >>> unique_paths(["first/path", ".\\first/path", "second/path"])
    ['first/path', 'second/path']
    """

    return set(os.path.normpath(p) for p in path_list)
  1. We got a set, not a list,
  2. Converting the set into a list needs to provide a consistent order,
  3. doctest uses eval so the "\" in ".\first" will be converted into "\".

We're looking for a simple string match, so we need to look for an easily matchable result string. You don't care about the separator, so either eliminate it or replace it, or test around it:

def unique_paths(path_list):
    """ Returns a list of normalized-unique paths based on path_list
    >>> paths = unique_paths(["first/path", ".\\\\first/path", "second/path"])
    >>> len(paths)
    2
    >>> [os.path.split(path) for path in sorted(list(paths))]
    [('first', 'path'), ('second', 'path')]
    # or heck, even
    >>> sorted(list(paths[0])).replace('\\\\', '/')
    'first/path'
    """
    return set(os.path.normpath(p) for p in path_list)
like image 164
kfsone Avatar answered Oct 02 '22 07:10

kfsone