9

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.

asherbret
  • 5,439
  • 4
  • 38
  • 58

3 Answers3

1

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)
kfsone
  • 23,617
  • 2
  • 42
  • 74
-3

You can get the os-dependent path separator by calling

sep = os.sep

and then process your relative paths for the platform you are working on. Or you can use

os.abspath()

instead. There are various options ( see http://pymotw.com/2/ospath/ ). I took the abspath-variant.

-Kim

KimKulling
  • 2,654
  • 1
  • 15
  • 26
-3

just ditch the pathname manipulation functions (os.path.join, os.path.split, etc). can you name a situation where / will not work? in my experience, those are few and far between, and all in the context of invoking windows-native programs in cmd.exe or command.com (those programs typically use / for options). outside of this particular scenario, forward slashes work in most situations in windows, and i'm willing to bet you're not targetting VAX/VMS.

just somebody
  • 18,602
  • 6
  • 51
  • 60
  • 2
    The OP was asking about tests, your answer indicates that in 2014 you may have needed to learn a lot more about testing. Non-/ separators can be introduced from all kinds of places: a) file content, b) user input, c) system() calls and pipe reads, d) sys.path, e) os.walk, f) fuse file systems, ... – kfsone Apr 19 '19 at 19:19