""" test_path.py - Test the path module.

This only runs on Posix and NT right now.  I would like to have more
tests.  You can help!  Just add appropriate pathnames for your
platform (os.name) in each place where the p() function is called.
Then send me the result.  If you can't get the test to run at all on
your platform, there's probably a bug in path.py -- please let me
know!

TempDirTestCase.testTouch() takes a while to run.  It sleeps a few
seconds to allow some time to pass between calls to check the modify
time on files.

URL:     http://www.jorendorff.com/articles/python/path
Author:  Jason Orendorff <jason@jorendorff.com>
Date:    7 Mar 2004

"""

import unittest
import codecs, os, random, shutil, tempfile, time
from path import Path


def p(**choices):
    """ Choose a value from several possible values, based on os.name """
    return choices[os.name]

class BasicTestCase(unittest.TestCase):
    def testConstructor(self):
        # constructor can be called with zero arguments, which is os.curdir
        p = Path()
        self.assertEquals(p.base(), os.curdir)

        # constructor can only be called with basestring(s) as argument
        self.assertRaises(ValueError, Path, None)
        self.assertRaises(ValueError, Path, 1)

        # calling with more than one argument joins them
        p1 = Path("a").joinwith("b")
        p2 = Path("a", "b")
        self.assertEquals(p1, p2)

    def testRelpath(self):
        root = Path(p(nt='C:\\',
                      posix='/'))
        foo = root / 'foo'
        quux =        foo / 'quux'
        bar =         foo / 'bar'
        boz =                bar / 'Baz' / 'Boz'
        up = Path(os.pardir)

        # basics
        self.assertEqual(root.relpathto(boz), Path('foo')/'bar'/'Baz'/'Boz')
        self.assertEqual(bar.relpathto(boz), Path('Baz')/'Boz')
        self.assertEqual(quux.relpathto(boz), up/'bar'/'Baz'/'Boz')
        self.assertEqual(boz.relpathto(quux), up/up/up/'quux')
        self.assertEqual(boz.relpathto(bar), up/up)

        # x.relpathto(x) == curdir
        self.assertEqual(root.relpathto(root), Path())
        self.assertEqual(boz.relpathto(boz), Path())
        # Make sure case is properly noted (or ignored)
        self.assertEqual(boz.relpathto(boz.normcase()), Path())

        # relpath()
        cwd = Path.cwd()
        self.assertEqual(boz.relpath(), cwd.relpathto(boz))

        if os.name == 'nt':
            # Check relpath across drives.
            d = Path('D:\\')
            self.assertEqual(d.relpathto(boz), boz)

    def testStringCompatibility(self):
        """ Test compatibility with ordinary strings. """
        x = Path('xyzzy')
        self.assertEqual(x, 'xyzzy')
        self.assertEqual(x, u'xyzzy')
        
        strx = x.base()
        self.assertEqual(strx, x)
        self.assert_(strx.__class__ in (str, unicode))

        # sorting
        items = [Path('fhj'),
                 Path('fgh'),
                 'E',
                 Path('d'),
                 'A',
                 Path('B'),
                 'c']
        items.sort()
        self.assert_(items == ['A', 'B', 'E', 'c', 'd', 'fgh', 'fhj'])

    def testProperties(self):
        # Create sample Path object.
        f = p(nt='C:\\Program Files\\Python\\Lib\\xyzzy.py',
              posix='/usr/local/python/lib/xyzzy.py')
        f = Path(f)

        # .directory
        self.assertEqual(f.directory, Path(p(nt='C:\\Program Files\\Python\\Lib',
                                             posix='/usr/local/python/lib')))

        # .basename
        self.assertEqual(f.basename, 'xyzzy.py')
        self.assertEqual(f.directory.basename, p(nt='Lib', posix='lib'))

        # .ext
        self.assertEqual(f.ext, '.py')
        self.assertEqual(f.directory.ext, '')

        # .drive
        self.assertEqual(f.drive, p(nt='C:', posix=''))

    def testMethods(self):
        # .abspath()
        self.assertEqual(Path().abspath(), Path.cwd())

        # .cwd()
        cwd = Path.cwd()
        self.assert_(isinstance(cwd, Path))
        if os.path.supports_unicode_filenames:
            self.assertEqual(unicode(cwd), os.getcwdu())
        else:
            self.assertEqual(str(cwd), os.getcwd())

    def testUNC(self):
        if hasattr(os.path, 'splitunc'):
            p = Path(r'\\python1\share1\dir1\file1.txt')
            self.assert_(p.uncshare == r'\\python1\share1')
            self.assert_(p.splitunc() == os.path.splitunc(str(p)))

    def testCompare(self):
        p1  = Path('/')
        lst = [p1, Path('/usr/'), Path('/usr/share')]
        p2  = Path('/')
        # a Path is not interned, so these are different objects
        assert p1 is not p2
        # but they must compare the same
        self.assert_(p1 == p2)
        self.assert_(p2 in lst)

class TempDirTestCase(unittest.TestCase):
    def setUp(self):
        # Create a temporary directory.
        f = tempfile.mktemp()
        system_tmp_dir = os.path.dirname(f)
        my_dir = 'testpath_tempdir_' + str(random.random())[2:]
        self.tempdir = os.path.join(system_tmp_dir, my_dir)
        os.mkdir(self.tempdir)

    def tearDown(self):
        shutil.rmtree(self.tempdir)

    def testTouch(self):
        # NOTE: This test takes a long time to run (~10 seconds).
        # It sleeps several seconds because on Windows, the resolution
        # of a file's mtime and ctime is about 2 seconds.
        #
        # atime isn't tested because on Windows the resolution of atime
        # is something like 24 hours.

        d = Path(self.tempdir)
        f = d / 'test.txt'
        t0 = time.time() - 3
        f.touch()
        t1 = time.time() + 3
        try:
            self.assert_(f.exists())
            self.assert_(f.isfile())
            self.assertEqual(f.getsize(), 0)
            self.assert_(t0 <= f.mtime() <= t1)
            if hasattr(os.path, 'getctime'):
                ct = f.ctime()
                self.assert_(t0 <= ct <= t1)

            time.sleep(5)
            fobj = file(f, 'ab')
            fobj.write('some bytes')
            fobj.close()

            time.sleep(5)
            t2 = time.time() - 3
            f.touch()
            t3 = time.time() + 3

            assert t0 <= t1 < t2 <= t3  # sanity check

            self.assert_(f.exists())
            self.assert_(f.isfile())
            self.assertEqual(f.getsize(), 10)
            self.assert_(t2 <= f.mtime() <= t3)
            if hasattr(os.path, 'getctime'):
                ct2 = f.ctime()
                if os.name == 'nt':
                    # On Windows, "ctime" is CREATION time
                    self.assertEqual(ct, ct2)
                    self.assert_(ct2 < t2)
                else:
                    # On other systems, it might be the CHANGE time 
                    # (especially on Unix, time of inode changes)
                    self.failUnless(ct == ct2 or ct2 == f.mtime())
        finally:
            f.remove()

    def testListing(self):
        d = Path(self.tempdir)
        self.assertEqual(d.listdir(), [])
        
        f = 'testfile.txt'
        af = d / f
        self.assertEqual(af, os.path.join(d, f))
        af.touch()
        try:
            self.assert_(af.exists())

            self.assertEqual(d.children(), [af])

            # .glob()
            self.assertEqual(d.glob('testfile.txt'), [af])
            self.assertEqual(d.glob('test*.txt'), [af])
            self.assertEqual(d.glob('*.txt'), [af])
            self.assertEqual(d.glob('*txt'), [af])
            self.assertEqual(d.glob('*'), [af])
            self.assertEqual(d.glob('*.html'), [])
            self.assertEqual(d.glob('testfile'), [])
        finally:
            af.remove()

        # Try a test with 20 files
        files = [d / ('%d.txt' % i) for i in range(20)]
        for f in files:
            fobj = file(f, 'w')
            fobj.write('some text\n')
            fobj.close()
        try:
            files2 = d.children()
            files.sort()
            files2.sort()
            self.assertEqual(files, files2)
        finally:
            for f in files:
                try:
                    f.remove()
                except:
                    pass

    def testMakeDirs(self):
        d = Path(self.tempdir)

        # Placeholder file so that when removedirs() is called,
        # it doesn't remove the temporary directory itself.
        tempf = d / 'temp.txt'
        tempf.touch()
        try:
            foo = d / 'foo'
            boz =      foo / 'bar' / 'baz' / 'boz'
            boz.makedirs()
            try:
                self.assert_(boz.isdir())
            finally:
                boz.removedirs()
            self.failIf(foo.exists())
            self.assert_(d.exists())

            foo.mkdir(0750)
            boz.makedirs(0700)
            try:
                self.assert_(boz.isdir())
            finally:
                boz.removedirs()
            self.failIf(foo.exists())
            self.assert_(d.exists())
        finally:
            os.remove(tempf)

    def assertSetsEqual(self, a, b):
        ad = {}
        for i in a: ad[i] = None
        bd = {}
        for i in b: bd[i] = None
        self.assertEqual(ad, bd)

    def testShutil(self):
        # Note: This only tests the methods exist and do roughly what
        # they should, neglecting the details as they are shutil's
        # responsibility.

        d = Path(self.tempdir)
        testDir = d / 'testdir'
        testFile = testDir / 'testfile.txt'
        testA = testDir / 'A'
        testCopy = testA / 'testcopy.txt'
        testLink = testA / 'testlink.txt'
        testB = testDir / 'B'
        testC = testB / 'C'
        testCopyOfLink = testC / testA.relpathto(testLink)

        # Create test dirs and a file
        testDir.mkdir()
        testA.mkdir()
        testB.mkdir()

        f = open(testFile, 'w')
        f.write('x' * 10000)
        f.close()

        # Test simple file copying.
        testFile.copyfile(testCopy)
        self.assert_(testCopy.isfile())
        self.assert_(testFile.read_file_bytes() == testCopy.read_file_bytes())

        # Test copying into a directory.
        testCopy2 = testA / testFile.basename
        testFile.copy(testA)
        self.assert_(testCopy2.isfile())
        self.assert_(testFile.read_file_bytes() == testCopy2.read_file_bytes())

        # Make a link for the next test to use.
        if hasattr(os, 'symlink'):
            testFile.symlink(testLink)
        else:
            testFile.copy(testLink)  # fallback

        # Test copying directory tree.
        testA.copytree(testC)
        self.assert_(testC.isdir())
        self.assertSetsEqual(
            testC.children(),
            [testC / testCopy.basename,
             testC / testFile.basename,
             testCopyOfLink])
        self.assert_(not testCopyOfLink.islink())

        # Clean up for another try.
        testC.rmtree()
        self.assert_(not testC.exists())

        # Copy again, preserving symlinks.
        testA.copytree(testC, True)
        self.assert_(testC.isdir())
        self.assertSetsEqual(
            testC.children(),
            [testC / testCopy.basename,
             testC / testFile.basename,
             testCopyOfLink])
        if hasattr(os, 'symlink'):
            self.assert_(testCopyOfLink.islink())
            self.assert_(testCopyOfLink.readlink() == testFile)

        # Clean up.
        testDir.rmtree()
        self.assert_(not testDir.exists())
        self.assertList(d.children(), [])

    def assertList(self, listing, expected):
        listing = list(listing)
        listing.sort()
        expected = list(expected)
        expected.sort()
        self.assertEqual(listing, expected)

    def testPatterns(self):
        d = Path(self.tempdir)
        names = [ 'x.tmp', 'x.xtmp', 'x2g', 'x22', 'x.txt' ]
        dirs = [d, d/'xdir', d/'xdir.tmp', d/'xdir.tmp'/'xsubdir']

        for e in dirs:
            if not e.isdir():  e.makedirs()
            for name in names:
                (e/name).touch()
        self.assertList(d.children('*.tmp'), [d/'x.tmp', d/'xdir.tmp'])
        self.assertList(d.files('*.tmp'), [d/'x.tmp'])
        self.assertList(d.dirs('*.tmp'), [d/'xdir.tmp'])
        self.assertList(d.walk(), [e for e in dirs if e != d] + [e/n for e in dirs for n in names])
        self.assertList(d.walk('*.tmp'),
                        [e/'x.tmp' for e in dirs] + [d/'xdir.tmp'])
        self.assertList(d.walkfiles('*.tmp'), [e/'x.tmp' for e in dirs])
        self.assertList(d.walkdirs('*.tmp'), [d/'xdir.tmp'])

    def testUnicode(self):
        d = Path(self.tempdir)
        p = d/'unicode.txt'

        def test(enc):
            """ Test that Path works with the specified encoding,
            which must be capable of representing the entire range of
            Unicode codepoints.
            """

            given = (u'Hello world\n'
                     u'\u0d0a\u0a0d\u0d15\u0a15\r\n'
                     u'\u0d0a\u0a0d\u0d15\u0a15\x85'
                     u'\u0d0a\u0a0d\u0d15\u0a15\u2028'
                     u'\r'
                     u'hanging')
            clean = (u'Hello world\n'
                     u'\u0d0a\u0a0d\u0d15\u0a15\n'
                     u'\u0d0a\u0a0d\u0d15\u0a15\n'
                     u'\u0d0a\u0a0d\u0d15\u0a15\n'
                     u'\n'
                     u'hanging')
            givenLines = [
                u'Hello world\n',
                u'\u0d0a\u0a0d\u0d15\u0a15\r\n',
                u'\u0d0a\u0a0d\u0d15\u0a15\x85',
                u'\u0d0a\u0a0d\u0d15\u0a15\u2028',
                u'\r',
                u'hanging']
            expectedLines = [
                u'Hello world\n',
                u'\u0d0a\u0a0d\u0d15\u0a15\n',
                u'\u0d0a\u0a0d\u0d15\u0a15\n',
                u'\u0d0a\u0a0d\u0d15\u0a15\n',
                u'\n',
                u'hanging']
            expectedLines2 = [
                u'Hello world',
                u'\u0d0a\u0a0d\u0d15\u0a15',
                u'\u0d0a\u0a0d\u0d15\u0a15',
                u'\u0d0a\u0a0d\u0d15\u0a15',
                u'',
                u'hanging']

            # write bytes manually to file
            f = codecs.open(p, 'w', enc)
            f.write(given)
            f.close()

            # test all 3 Path read-fully functions, including
            # Path.read_file_lines() in unicode mode.
            self.assertEqual(p.read_file_bytes(), given.encode(enc))
            self.assertEqual(p.read_file_text(enc), clean)
            self.assertEqual(p.read_file_lines(enc), expectedLines)
            self.assertEqual(p.read_file_lines(enc, retain=False), expectedLines2)

            # If this is UTF-16, that's enough.
            # The rest of these will unfortunately fail because append=True mode
            # causes an extra BOM to be written in the middle of the file.
            # UTF-16 is the only encoding that has this problem.
            if enc == 'UTF-16':
                return

            # Write Unicode to file using Path.write_file_text().
            cleanNoHanging = clean + u'\n'  # This test doesn't work with a hanging line.
            p.write_file_text(cleanNoHanging, enc)
            p.write_file_text(cleanNoHanging, enc, append=True)
            # Check the result.
            expectedBytes = 2 * cleanNoHanging.replace('\n', os.linesep).encode(enc)
            expectedLinesNoHanging = expectedLines[:]
            expectedLinesNoHanging[-1] += '\n'
            self.assertEqual(p.read_file_bytes(), expectedBytes)
            self.assertEqual(p.read_file_text(enc), 2 * cleanNoHanging)
            self.assertEqual(p.read_file_lines(enc), 2 * expectedLinesNoHanging)
            self.assertEqual(p.read_file_lines(enc, retain=False), 2 * expectedLines2)

            # Write Unicode to file using Path.write_file_lines().
            # The output in the file should be exactly the same as last time.
            p.write_file_lines(expectedLines, enc)
            p.write_file_lines(expectedLines2, enc, append=True)
            # Check the result.
            self.assertEqual(p.read_file_bytes(), expectedBytes)

            # Now: same test, but using various newline sequences.
            # If linesep is being properly applied, these will be converted
            # to the platform standard newline sequence.
            p.write_file_lines(givenLines, enc)
            p.write_file_lines(givenLines, enc, append=True)
            # Check the result.
            self.assertEqual(p.read_file_bytes(), expectedBytes)

            # Same test, using newline sequences that are different
            # from the platform default.
            def testLinesep(eol):
                p.write_file_lines(givenLines, enc, linesep=eol)
                p.write_file_lines(givenLines, enc, linesep=eol, append=True)
                expected = 2 * cleanNoHanging.replace(u'\n', eol).encode(enc)
                self.assertEqual(p.read_file_bytes(), expected)

            testLinesep(u'\n')
            testLinesep(u'\r')
            testLinesep(u'\r\n')
            testLinesep(u'\x0d\x85')


            # Again, but with linesep=None.
            p.write_file_lines(givenLines, enc, linesep=None)
            p.write_file_lines(givenLines, enc, linesep=None, append=True)
            # Check the result.
            expectedBytes = 2 * given.encode(enc)
            self.assertEqual(p.read_file_bytes(), expectedBytes)
            self.assertEqual(p.read_file_text(enc), 2 * clean)
            expectedResultLines = expectedLines[:]
            expectedResultLines[-1] += expectedLines[0]
            expectedResultLines += expectedLines[1:]
            self.assertEqual(p.read_file_lines(enc), expectedResultLines)

        test('UTF-8')
        test('UTF-16BE')
        test('UTF-16LE')
        test('UTF-16')

if __name__ == '__main__':
    unittest.main()
