1import py
2import os, sys
3import pytest
4from py._path.svnwc import InfoSvnWCCommand, XMLWCStatus, parse_wcinfotime
5from py._path import svnwc as svncommon
6from svntestbase import CommonSvnTests
7
8
9pytestmark = pytest.mark.xfail(sys.platform.startswith('win'),
10                               reason='#161 all tests in this file are failing on Windows',
11                               run=False)
12
13
14def test_make_repo(path1, tmpdir):
15    repo = tmpdir.join("repo")
16    py.process.cmdexec('svnadmin create %s' % repo)
17    if sys.platform == 'win32':
18        repo = '/' + str(repo).replace('\\', '/')
19    repo = py.path.svnurl("file://%s" % repo)
20    wc = py.path.svnwc(tmpdir.join("wc"))
21    wc.checkout(repo)
22    assert wc.rev == 0
23    assert len(wc.listdir()) == 0
24    p = wc.join("a_file")
25    p.write("test file")
26    p.add()
27    rev = wc.commit("some test")
28    assert p.info().rev == 1
29    assert rev == 1
30    rev = wc.commit()
31    assert rev is None
32
33def pytest_funcarg__path1(request):
34    repo, repourl, wc = request.getfuncargvalue("repowc1")
35    return wc
36
37class TestWCSvnCommandPath(CommonSvnTests):
38    def test_status_attributes_simple(self, path1):
39        def assert_nochange(p):
40            s = p.status()
41            assert not s.modified
42            assert not s.prop_modified
43            assert not s.added
44            assert not s.deleted
45            assert not s.replaced
46
47        dpath = path1.join('sampledir')
48        assert_nochange(path1.join('sampledir'))
49        assert_nochange(path1.join('samplefile'))
50
51    def test_status_added(self, path1):
52        nf = path1.join('newfile')
53        nf.write('hello')
54        nf.add()
55        try:
56            s = nf.status()
57            assert s.added
58            assert not s.modified
59            assert not s.prop_modified
60            assert not s.replaced
61        finally:
62            nf.revert()
63
64    def test_status_change(self, path1):
65        nf = path1.join('samplefile')
66        try:
67            nf.write(nf.read() + 'change')
68            s = nf.status()
69            assert not s.added
70            assert s.modified
71            assert not s.prop_modified
72            assert not s.replaced
73        finally:
74            nf.revert()
75
76    def test_status_added_ondirectory(self, path1):
77        sampledir = path1.join('sampledir')
78        try:
79            t2 = sampledir.mkdir('t2')
80            t1 = t2.join('t1')
81            t1.write('test')
82            t1.add()
83            s = sampledir.status(rec=1)
84            # Comparing just the file names, because paths are unpredictable
85            # on Windows. (long vs. 8.3 paths)
86            assert t1.basename in [item.basename for item in s.added]
87            assert t2.basename in [item.basename for item in s.added]
88        finally:
89            t2.revert(rec=1)
90            t2.localpath.remove(rec=1)
91
92    def test_status_unknown(self, path1):
93        t1 = path1.join('un1')
94        try:
95            t1.write('test')
96            s = path1.status()
97            # Comparing just the file names, because paths are unpredictable
98            # on Windows. (long vs. 8.3 paths)
99            assert t1.basename in [item.basename for item in s.unknown]
100        finally:
101            t1.localpath.remove()
102
103    def test_status_unchanged(self, path1):
104        r = path1
105        s = path1.status(rec=1)
106        # Comparing just the file names, because paths are unpredictable
107        # on Windows. (long vs. 8.3 paths)
108        assert r.join('samplefile').basename in [item.basename
109                                                    for item in s.unchanged]
110        assert r.join('sampledir').basename in [item.basename
111                                                    for item in s.unchanged]
112        assert r.join('sampledir/otherfile').basename in [item.basename
113                                                    for item in s.unchanged]
114
115    def test_status_update(self, path1):
116        # not a mark because the global "pytestmark" will end up overwriting a mark here
117        pytest.xfail("svn-1.7 has buggy 'status --xml' output")
118        r = path1
119        try:
120            r.update(rev=1)
121            s = r.status(updates=1, rec=1)
122            # Comparing just the file names, because paths are unpredictable
123            # on Windows. (long vs. 8.3 paths)
124            import pprint
125            pprint.pprint(s.allpath())
126            assert r.join('anotherfile').basename in [item.basename for
127                                                    item in s.update_available]
128            #assert len(s.update_available) == 1
129        finally:
130            r.update()
131
132    def test_status_replaced(self, path1):
133        p = path1.join("samplefile")
134        p.remove()
135        p.ensure(dir=0)
136        try:
137            s = path1.status()
138            assert p.basename in [item.basename for item in s.replaced]
139        finally:
140            path1.revert(rec=1)
141
142    def test_status_ignored(self, path1):
143        try:
144            d = path1.join('sampledir')
145            p = py.path.local(d).join('ignoredfile')
146            p.ensure(file=True)
147            s = d.status()
148            assert [x.basename for x in s.unknown] == ['ignoredfile']
149            assert [x.basename for x in s.ignored] == []
150            d.propset('svn:ignore', 'ignoredfile')
151            s = d.status()
152            assert [x.basename for x in s.unknown] == []
153            assert [x.basename for x in s.ignored] == ['ignoredfile']
154        finally:
155            path1.revert(rec=1)
156
157    def test_status_conflict(self, path1, tmpdir):
158        wc = path1
159        wccopy = py.path.svnwc(tmpdir.join("conflict_copy"))
160        wccopy.checkout(wc.url)
161        p = wc.ensure('conflictsamplefile', file=1)
162        p.write('foo')
163        wc.commit('added conflictsamplefile')
164        wccopy.update()
165        assert wccopy.join('conflictsamplefile').check()
166        p.write('bar')
167        wc.commit('wrote some data')
168        wccopy.join('conflictsamplefile').write('baz')
169        wccopy.update(interactive=False)
170        s = wccopy.status()
171        assert [x.basename for x in s.conflict] == ['conflictsamplefile']
172
173    def test_status_external(self, path1, repowc2):
174        otherrepo, otherrepourl, otherwc = repowc2
175        d = path1.ensure('sampledir', dir=1)
176        try:
177            d.update()
178            d.propset('svn:externals', 'otherwc %s' % (otherwc.url,))
179            d.update()
180            s = d.status()
181            assert [x.basename for x in s.external] == ['otherwc']
182            assert 'otherwc' not in [x.basename for x in s.unchanged]
183            s = d.status(rec=1)
184            assert [x.basename for x in s.external] == ['otherwc']
185            assert 'otherwc' in [x.basename for x in s.unchanged]
186        finally:
187            path1.revert(rec=1)
188
189    def test_status_deleted(self, path1):
190        d = path1.ensure('sampledir', dir=1)
191        d.remove()
192        d.ensure(dir=1)
193        path1.commit()
194        d.ensure('deletefile', dir=0)
195        d.commit()
196        s = d.status()
197        assert 'deletefile' in [x.basename for x in s.unchanged]
198        assert not s.deleted
199        p = d.join('deletefile')
200        p.remove()
201        s = d.status()
202        assert 'deletefile' not in s.unchanged
203        assert [x.basename for x in s.deleted] == ['deletefile']
204
205    def test_status_noauthor(self, path1):
206        # testing for XML without author - this used to raise an exception
207        xml = '''\
208        <entry path="/tmp/pytest-23/wc">
209        <wc-status item="normal" props="none" revision="0">
210        <commit revision="0">
211        <date>2008-08-19T16:50:53.400198Z</date>
212        </commit>
213        </wc-status>
214        </entry>
215        '''
216        XMLWCStatus.fromstring(xml, path1)
217
218    def test_status_wrong_xml(self, path1):
219        # testing for XML without author - this used to raise an exception
220        xml = '<entry path="/home/jean/zope/venv/projectdb/parts/development-products/DataGridField">\n<wc-status item="incomplete" props="none" revision="784">\n</wc-status>\n</entry>'
221        st = XMLWCStatus.fromstring(xml, path1)
222        assert len(st.incomplete) == 1
223
224    def test_diff(self, path1):
225        p = path1 / 'anotherfile'
226        out = p.diff(rev=2)
227        assert out.find('hello') != -1
228
229    def test_blame(self, path1):
230        p = path1.join('samplepickle')
231        lines = p.blame()
232        assert sum([l[0] for l in lines]) == len(lines)
233        for l1, l2 in zip(p.readlines(), [l[2] for l in lines]):
234            assert l1 == l2
235        assert [l[1] for l in lines] == ['hpk'] * len(lines)
236        p = path1.join('samplefile')
237        lines = p.blame()
238        assert sum([l[0] for l in lines]) == len(lines)
239        for l1, l2 in zip(p.readlines(), [l[2] for l in lines]):
240            assert l1 == l2
241        assert [l[1] for l in lines] == ['hpk'] * len(lines)
242
243    def test_join_abs(self, path1):
244        s = str(path1.localpath)
245        n = path1.join(s, abs=1)
246        assert path1 == n
247
248    def test_join_abs2(self, path1):
249        assert path1.join('samplefile', abs=1) == path1.join('samplefile')
250
251    def test_str_gives_localpath(self, path1):
252        assert str(path1) == str(path1.localpath)
253
254    def test_versioned(self, path1):
255        assert path1.check(versioned=1)
256        # TODO: Why does my copy of svn think .svn is versioned?
257        #assert path1.join('.svn').check(versioned=0)
258        assert path1.join('samplefile').check(versioned=1)
259        assert not path1.join('notexisting').check(versioned=1)
260        notexisting = path1.join('hello').localpath
261        try:
262            notexisting.write("")
263            assert path1.join('hello').check(versioned=0)
264        finally:
265            notexisting.remove()
266
267    def test_listdir_versioned(self, path1):
268        assert path1.check(versioned=1)
269        p = path1.localpath.ensure("not_a_versioned_file")
270        l = [x.localpath
271                for x in path1.listdir(lambda x: x.check(versioned=True))]
272        assert p not in l
273
274    def test_nonversioned_remove(self, path1):
275        assert path1.check(versioned=1)
276        somefile = path1.join('nonversioned/somefile')
277        nonwc = py.path.local(somefile)
278        nonwc.ensure()
279        assert somefile.check()
280        assert not somefile.check(versioned=True)
281        somefile.remove() # this used to fail because it tried to 'svn rm'
282
283    def test_properties(self, path1):
284        try:
285            path1.propset('gaga', 'this')
286            assert path1.propget('gaga') == 'this'
287            # Comparing just the file names, because paths are unpredictable
288            # on Windows. (long vs. 8.3 paths)
289            assert path1.basename in [item.basename for item in
290                                        path1.status().prop_modified]
291            assert 'gaga' in path1.proplist()
292            assert path1.proplist()['gaga'] == 'this'
293
294        finally:
295            path1.propdel('gaga')
296
297    def test_proplist_recursive(self, path1):
298        s = path1.join('samplefile')
299        s.propset('gugu', 'that')
300        try:
301            p = path1.proplist(rec=1)
302            # Comparing just the file names, because paths are unpredictable
303            # on Windows. (long vs. 8.3 paths)
304            assert (path1 / 'samplefile').basename in [item.basename
305                                                                for item in p]
306        finally:
307            s.propdel('gugu')
308
309    def test_long_properties(self, path1):
310        value = """
311        vadm:posix : root root 0100755
312        Properties on 'chroot/dns/var/bind/db.net.xots':
313                """
314        try:
315            path1.propset('gaga', value)
316            backvalue = path1.propget('gaga')
317            assert backvalue == value
318            #assert len(backvalue.split('\n')) == 1
319        finally:
320            path1.propdel('gaga')
321
322
323    def test_ensure(self, path1):
324        newpath = path1.ensure('a', 'b', 'c')
325        try:
326            assert newpath.check(exists=1, versioned=1)
327            newpath.write("hello")
328            newpath.ensure()
329            assert newpath.read() == "hello"
330        finally:
331            path1.join('a').remove(force=1)
332
333    def test_not_versioned(self, path1):
334        p = path1.localpath.mkdir('whatever')
335        f = path1.localpath.ensure('testcreatedfile')
336        try:
337            assert path1.join('whatever').check(versioned=0)
338            assert path1.join('testcreatedfile').check(versioned=0)
339            assert not path1.join('testcreatedfile').check(versioned=1)
340        finally:
341            p.remove(rec=1)
342            f.remove()
343
344    def test_lock_unlock(self, path1):
345        root = path1
346        somefile = root.join('somefile')
347        somefile.ensure(file=True)
348        # not yet added to repo
349        py.test.raises(Exception, 'somefile.lock()')
350        somefile.write('foo')
351        somefile.commit('test')
352        assert somefile.check(versioned=True)
353        somefile.lock()
354        try:
355            locked = root.status().locked
356            assert len(locked) == 1
357            assert locked[0].basename == somefile.basename
358            assert locked[0].dirpath().basename == somefile.dirpath().basename
359            #assert somefile.locked()
360            py.test.raises(Exception, 'somefile.lock()')
361        finally:
362            somefile.unlock()
363        #assert not somefile.locked()
364        locked = root.status().locked
365        assert locked == []
366        py.test.raises(Exception, 'somefile,unlock()')
367        somefile.remove()
368
369    def test_commit_nonrecursive(self, path1):
370        somedir = path1.join('sampledir')
371        somedir.mkdir("subsubdir")
372        somedir.propset('foo', 'bar')
373        status = somedir.status()
374        assert len(status.prop_modified) == 1
375        assert len(status.added) == 1
376
377        somedir.commit('non-recursive commit', rec=0)
378        status = somedir.status()
379        assert len(status.prop_modified) == 0
380        assert len(status.added) == 1
381
382        somedir.commit('recursive commit')
383        status = somedir.status()
384        assert len(status.prop_modified) == 0
385        assert len(status.added) == 0
386
387    def test_commit_return_value(self, path1):
388        testfile = path1.join('test.txt').ensure(file=True)
389        testfile.write('test')
390        rev = path1.commit('testing')
391        assert type(rev) == int
392
393        anotherfile = path1.join('another.txt').ensure(file=True)
394        anotherfile.write('test')
395        rev2 = path1.commit('testing more')
396        assert type(rev2) == int
397        assert rev2 == rev + 1
398
399    #def test_log(self, path1):
400    #   l = path1.log()
401    #   assert len(l) == 3  # might need to be upped if more tests are added
402
403class XTestWCSvnCommandPathSpecial:
404
405    rooturl = 'http://codespeak.net/svn/py.path/trunk/dist/py.path/test/data'
406    #def test_update_none_rev(self, path1):
407    #    path = tmpdir.join('checkouttest')
408    #    wcpath = newpath(xsvnwc=str(path), url=path1url)
409    #    try:
410    #        wcpath.checkout(rev=2100)
411    #        wcpath.update()
412    #        assert wcpath.info().rev > 2100
413    #    finally:
414    #        wcpath.localpath.remove(rec=1)
415
416def test_parse_wcinfotime():
417    assert (parse_wcinfotime('2006-05-30 20:45:26 +0200 (Tue, 30 May 2006)') ==
418            1149021926)
419    assert (parse_wcinfotime('2003-10-27 20:43:14 +0100 (Mon, 27 Oct 2003)') ==
420            1067287394)
421
422class TestInfoSvnWCCommand:
423
424    def test_svn_1_2(self, path1):
425        output = """
426        Path: test_svnwc.py
427        Name: test_svnwc.py
428        URL: http://codespeak.net/svn/py/dist/py/path/svn/wccommand.py
429        Repository UUID: fd0d7bf2-dfb6-0310-8d31-b7ecfe96aada
430        Revision: 28137
431        Node Kind: file
432        Schedule: normal
433        Last Changed Author: jan
434        Last Changed Rev: 27939
435        Last Changed Date: 2006-05-30 20:45:26 +0200 (Tue, 30 May 2006)
436        Text Last Updated: 2006-06-01 00:42:53 +0200 (Thu, 01 Jun 2006)
437        Properties Last Updated: 2006-05-23 11:54:59 +0200 (Tue, 23 May 2006)
438        Checksum: 357e44880e5d80157cc5fbc3ce9822e3
439        """
440        path = py.path.local(__file__).dirpath().chdir()
441        try:
442            info = InfoSvnWCCommand(output)
443        finally:
444            path.chdir()
445        assert info.last_author == 'jan'
446        assert info.kind == 'file'
447        assert info.mtime == 1149021926.0
448        assert info.url == 'http://codespeak.net/svn/py/dist/py/path/svn/wccommand.py'
449        assert info.time == 1149021926000000.0
450        assert info.rev == 28137
451
452
453    def test_svn_1_3(self, path1):
454        output = """
455        Path: test_svnwc.py
456        Name: test_svnwc.py
457        URL: http://codespeak.net/svn/py/dist/py/path/svn/wccommand.py
458        Repository Root: http://codespeak.net/svn
459        Repository UUID: fd0d7bf2-dfb6-0310-8d31-b7ecfe96aada
460        Revision: 28124
461        Node Kind: file
462        Schedule: normal
463        Last Changed Author: jan
464        Last Changed Rev: 27939
465        Last Changed Date: 2006-05-30 20:45:26 +0200 (Tue, 30 May 2006)
466        Text Last Updated: 2006-06-02 23:46:11 +0200 (Fri, 02 Jun 2006)
467        Properties Last Updated: 2006-06-02 23:45:28 +0200 (Fri, 02 Jun 2006)
468        Checksum: 357e44880e5d80157cc5fbc3ce9822e3
469        """
470        path = py.path.local(__file__).dirpath().chdir()
471        try:
472            info = InfoSvnWCCommand(output)
473        finally:
474            path.chdir()
475        assert info.last_author == 'jan'
476        assert info.kind == 'file'
477        assert info.mtime == 1149021926.0
478        assert info.url == 'http://codespeak.net/svn/py/dist/py/path/svn/wccommand.py'
479        assert info.rev == 28124
480        assert info.time == 1149021926000000.0
481
482
483def test_characters_at():
484    py.test.raises(ValueError, "py.path.svnwc('/tmp/@@@:')")
485
486def test_characters_tilde():
487    py.path.svnwc('/tmp/test~')
488
489
490class TestRepo:
491    def test_trailing_slash_is_stripped(self, path1):
492        # XXX we need to test more normalizing properties
493        url = path1.join("/")
494        assert path1 == url
495
496    #def test_different_revs_compare_unequal(self, path1):
497    #    newpath = path1.new(rev=1199)
498    #    assert newpath != path1
499
500    def test_exists_svn_root(self, path1):
501        assert path1.check()
502
503    #def test_not_exists_rev(self, path1):
504    #    url = path1.__class__(path1url, rev=500)
505    #    assert url.check(exists=0)
506
507    #def test_nonexisting_listdir_rev(self, path1):
508    #    url = path1.__class__(path1url, rev=500)
509    #    raises(py.error.ENOENT, url.listdir)
510
511    #def test_newrev(self, path1):
512    #    url = path1.new(rev=None)
513    #    assert url.rev == None
514    #    assert url.strpath == path1.strpath
515    #    url = path1.new(rev=10)
516    #    assert url.rev == 10
517
518    #def test_info_rev(self, path1):
519    #    url = path1.__class__(path1url, rev=1155)
520    #    url = url.join("samplefile")
521    #    res = url.info()
522    #    assert res.size > len("samplefile") and res.created_rev == 1155
523
524    # the following tests are easier if we have a path class
525    def test_repocache_simple(self, path1):
526        repocache = svncommon.RepoCache()
527        repocache.put(path1.strpath, 42)
528        url, rev = repocache.get(path1.join('test').strpath)
529        assert rev == 42
530        assert url == path1.strpath
531
532    def test_repocache_notimeout(self, path1):
533        repocache = svncommon.RepoCache()
534        repocache.timeout = 0
535        repocache.put(path1.strpath, path1.rev)
536        url, rev = repocache.get(path1.strpath)
537        assert rev == -1
538        assert url == path1.strpath
539
540    def test_repocache_outdated(self, path1):
541        repocache = svncommon.RepoCache()
542        repocache.put(path1.strpath, 42, timestamp=0)
543        url, rev = repocache.get(path1.join('test').strpath)
544        assert rev == -1
545        assert url == path1.strpath
546
547    def _test_getreporev(self):
548        """ this test runs so slow it's usually disabled """
549        old = svncommon.repositories.repos
550        try:
551            _repocache.clear()
552            root = path1.new(rev=-1)
553            url, rev = cache.repocache.get(root.strpath)
554            assert rev>=0
555            assert url == svnrepourl
556        finally:
557            repositories.repos = old
558