1# -*- coding: utf-8 -*-
2#
3# Copyright (C) 2006-2021 Edgewall Software
4# All rights reserved.
5#
6# This software is licensed as described in the file COPYING, which
7# you should have received as part of this distribution. The terms
8# are also available at https://trac.edgewall.com/license.html.
9#
10# This software consists of voluntary contributions made by many
11# individuals. For the exact contribution history, see the revision
12# history and logs, available at https://trac.edgewall.org/.
13
14import unittest
15
16from trac.test import Mock
17from trac.versioncontrol.api import *
18from trac.versioncontrol.web_ui import *
19from trac.wiki.tests import formatter
20
21
22YOUNGEST_REV = 200
23
24
25def _get_changeset(rev):
26    if rev == '1':
27        return Mock(message="start", is_viewable=lambda perm: True)
28    else:
29        raise NoSuchChangeset(rev)
30
31
32def _normalize_rev(rev):
33    if rev is None or rev in ('', 'head'):
34        return YOUNGEST_REV
35    try:
36        nrev = int(rev)
37        if nrev <= YOUNGEST_REV:
38            return nrev
39    except ValueError:
40        pass
41    raise NoSuchChangeset(rev)
42
43
44def _get_node(path, rev=None):
45    if path == 'foo':
46        return Mock(path=path, rev=rev, isfile=False,
47                    is_viewable=lambda resource: True)
48    elif path == 'missing/file':
49        raise NoSuchNode(path, rev)
50    else:
51        return Mock(path=path, rev=rev, isfile=True,
52                    is_viewable=lambda resource: True)
53
54
55class GitRepositoryStub(object):
56
57    has_linear_changesets = False
58
59    _revs = [
60        ('ffffffffffffffffffffffffffffffffffffffff', ('HEAD',)),
61        ('deadbef222222222222222222222222222222222', ('1.0-stable',
62                                                      '1.0-stáblé')),
63        ('deadbef111111111111111111111111111111111', ('v1.0.1', 'vér1.0.1')),
64        ('deadbef000000000000000000000000000000000', ('v1.0', 'vér1.0')),
65        ('deadbeefffffffffffffffffffffffffffffffff', ('0.12-stable',
66                                                      '0.12-stáblé')),
67        ('0000009876543210987654321098765432109876', ()),  # only digits
68        ('0000001234567890123456789012345678901234', ()),
69        ('1111111111111111111111111111111111111111', ()),  # oldest rev
70    ]
71
72    def __init__(self, reponame):
73        self.reponame = reponame
74        self.resource = Resource('repository', self.reponame)
75        self.youngest_rev = 'ffffffffffffffffffffffffffffffffffffffff'
76        self.oldest_rev = '1111111111111111111111111111111111111111'
77
78    def get_changeset(self, rev):
79        nrev = None
80        if not rev:
81            nrev = self.youngest_rev
82        else:
83            revs = [r for r, names in self._revs if r.startswith(rev)]
84            if len(revs) == 1:
85                nrev = revs[0]
86            else:
87                for r, names in self._revs:
88                    if rev in names:
89                        nrev = r
90                        break
91        if nrev:
92            return Mock(repos=self, rev=nrev, message='message %s' % nrev[:8],
93                        author='trac', is_viewable=lambda perm: True)
94        raise NoSuchChangeset(rev)
95
96    def normalize_rev(self, rev):
97        cset = self.get_changeset(rev)
98        return cset.rev
99
100    def get_node(self, path, rev=None):
101        return _get_node(path, rev)
102
103
104def _get_repository(reponame):
105    if reponame.endswith('.git'):
106        return GitRepositoryStub(reponame)
107    return Mock(reponame=reponame, youngest_rev=YOUNGEST_REV,
108                get_changeset=_get_changeset, get_node=_get_node,
109                normalize_rev=_normalize_rev,
110                has_linear_changesets=True,
111                resource=Resource('repository', reponame))
112
113
114def _get_all_repositories():
115    return {'': {}, 'trac.git': {}}
116
117
118def repository_setup(tc):
119    setattr(RepositoryManager(tc.env), 'get_repository', _get_repository)
120    setattr(RepositoryManager(tc.env), 'get_all_repositories',
121            _get_all_repositories)
122
123
124CHANGESET_TEST_CASES = """
125============================== changeset: link resolver
126changeset:1
127changeset:12
128changeset:abc
129changeset:1, changeset:1/README.txt
130------------------------------
131<p>
132<a class="changeset" href="/changeset/1" title="start">changeset:1</a>
133<a class="missing changeset" title="No changeset 12 in the repository">changeset:12</a>
134<a class="missing changeset" title="No changeset abc in the repository">changeset:abc</a>
135<a class="changeset" href="/changeset/1" title="start">changeset:1</a>, <a class="changeset" href="/changeset/1/README.txt" title="start">changeset:1/README.txt</a>
136</p>
137------------------------------
138============================== changeset: link resolver + query and fragment
139changeset:1?format=diff
140changeset:1#file0
141------------------------------
142<p>
143<a class="changeset" href="/changeset/1?format=diff" title="start">changeset:1?format=diff</a>
144<a class="changeset" href="/changeset/1#file0" title="start">changeset:1#file0</a>
145</p>
146------------------------------
147============================== changeset shorthand syntax
148[1], r1
149[12], r12, rABC
150[1/README.txt], r1/trunk, rABC/trunk
151------------------------------
152<p>
153<a class="changeset" href="/changeset/1" title="start">[1]</a>, <a class="changeset" href="/changeset/1" title="start">r1</a>
154<a class="missing changeset" title="No changeset 12 in the repository">[12]</a>, <a class="missing changeset" title="No changeset 12 in the repository">r12</a>, rABC
155<a class="changeset" href="/changeset/1/README.txt" title="start">[1/README.txt]</a>, <a class="changeset" href="/changeset/1/trunk" title="start">r1/trunk</a>, rABC/trunk
156</p>
157------------------------------
158============================== changeset shorthand syntax + query and fragment
159[1?format=diff]
160[1#file0]
161[1/README.txt?format=diff]
162[1/README.txt#file0]
163------------------------------
164<p>
165<a class="changeset" href="/changeset/1?format=diff" title="start">[1?format=diff]</a>
166<a class="changeset" href="/changeset/1#file0" title="start">[1#file0]</a>
167<a class="changeset" href="/changeset/1/README.txt?format=diff" title="start">[1/README.txt?format=diff]</a>
168<a class="changeset" href="/changeset/1/README.txt#file0" title="start">[1/README.txt#file0]</a>
169</p>
170------------------------------
171============================== escaping the above
172![1], !r1
173------------------------------
174<p>
175[1], r1
176</p>
177------------------------------
178============================== unicode digits
179[₁₂₃], r₁₂₃, [₀A₁B₂C₃D]
180------------------------------
181<p>
182[₁₂₃], r₁₂₃, [₀A₁B₂C₃D]
183</p>
184------------------------------
185============================== Link resolver counter examples
186Change:[10] There should be a link to changeset [10]
187
188rfc and rfc:4180 should not be changeset links, neither should rfc4180
189------------------------------
190<p>
191Change:<a class="missing changeset" title="No changeset 10 in the repository">[10]</a> There should be a link to changeset <a class="missing changeset" title="No changeset 10 in the repository">[10]</a>
192</p>
193<p>
194rfc and rfc:4180 should not be changeset links, neither should rfc4180
195</p>
196------------------------------
197Change:<a class="missing changeset" title="No changeset 10 in the repository">[10]</a> There should be a link to changeset <a class="missing changeset" title="No changeset 10 in the repository">[10]</a>
198
199rfc and rfc:4180 should not be changeset links, neither should rfc4180
200============================== InterTrac for changesets
201trac:changeset:2081
202[trac:changeset:2081 Trac r2081]
203------------------------------
204<p>
205<a class="ext-link" href="https://trac.edgewall.org/intertrac/changeset%3A2081" title="changeset:2081 in The Trac Project"><span class="icon"></span>trac:changeset:2081</a>
206<a class="ext-link" href="https://trac.edgewall.org/intertrac/changeset%3A2081" title="changeset:2081 in The Trac Project"><span class="icon"></span>Trac r2081</a>
207</p>
208------------------------------
209============================== Changeset InterTrac shorthands
210[T2081]
211[trac 2081]
212[trac 2081/trunk]
213T:r2081
214------------------------------
215<p>
216<a class="ext-link" href="https://trac.edgewall.org/intertrac/changeset%3A2081" title="changeset:2081 in The Trac Project"><span class="icon"></span>[T2081]</a>
217<a class="ext-link" href="https://trac.edgewall.org/intertrac/changeset%3A2081" title="changeset:2081 in The Trac Project"><span class="icon"></span>[trac 2081]</a>
218<a class="ext-link" href="https://trac.edgewall.org/intertrac/changeset%3A2081/trunk" title="changeset:2081/trunk in The Trac Project"><span class="icon"></span>[trac 2081/trunk]</a>
219<a class="ext-link" href="https://trac.edgewall.org/intertrac/r2081" title="r2081 in The Trac Project"><span class="icon"></span>T:r2081</a>
220</p>
221------------------------------
222""" #"
223
224
225LOG_TEST_CASES = """
226============================== Log range TracLinks
227[1:2], r1:2, [12:23], r12:23
228[1:2/trunk], r1:2/trunk
229[2:1/trunk] reversed, r2:1/trunk reversed
230------------------------------
231<p>
232<a class="source" href="/log/?revs=1-2">[1:2]</a>, <a class="source" href="/log/?revs=1-2">r1:2</a>, <a class="source" href="/log/?revs=12-23">[12:23]</a>, <a class="source" href="/log/?revs=12-23">r12:23</a>
233<a class="source" href="/log/trunk?revs=1-2">[1:2/trunk]</a>, <a class="source" href="/log/trunk?revs=1-2">r1:2/trunk</a>
234<a class="source" href="/log/trunk?revs=1-2">[2:1/trunk]</a> reversed, <a class="source" href="/log/trunk?revs=1-2">r2:1/trunk</a> reversed
235</p>
236------------------------------
237============================== changeset and log shorthand syntax with hash ids
238[deadbeef/trac.git]
239[deadbeef/trac.git/trac]
240[deadbeef:deadbef1/trac.git]
241[deadbeef:deadbef1/trac.git/trac]
242------------------------------
243<p>
244<a class="changeset" href="/changeset/deadbeef/trac.git" title="message deadbeef">[deadbeef/trac.git]</a>
245<a class="changeset" href="/changeset/deadbeef/trac.git/trac" title="message deadbeef">[deadbeef/trac.git/trac]</a>
246<a class="source" href="/log/trac.git/?revs=deadbeef%3Adeadbef1">[deadbeef:deadbef1/trac.git]</a>
247<a class="source" href="/log/trac.git/trac?revs=deadbeef%3Adeadbef1">[deadbeef:deadbef1/trac.git/trac]</a>
248</p>
249------------------------------
250============================== changeset and log with digit hash on non linear changesets
251[00000012/trac.git]
252[00000012/trac.git/trac]
253[00000012:00000098/trac.git]
254[00000012:00000098/trac.git/trac]
255------------------------------
256<p>
257<a class="changeset" href="/changeset/00000012/trac.git" title="message 00000012">[00000012/trac.git]</a>
258<a class="changeset" href="/changeset/00000012/trac.git/trac" title="message 00000012">[00000012/trac.git/trac]</a>
259<a class="source" href="/log/trac.git/?revs=00000012%3A00000098">[00000012:00000098/trac.git]</a>
260<a class="source" href="/log/trac.git/trac?revs=00000012%3A00000098">[00000012:00000098/trac.git/trac]</a>
261</p>
262------------------------------
263============================== Big ranges (#9955 regression)
264[1234567890:12345678901]
265------------------------------
266<p>
267<a class="source" href="/log/?revs=1234567890-12345678901">[1234567890:12345678901]</a>
268</p>
269------------------------------
270<a class="source" href="/log/?revs=1234567890-12345678901">[1234567890:12345678901]</a>
271============================== Escaping Log range TracLinks
272![1:2], !r1:2, ![12:23], !r12:23
273------------------------------
274<p>
275[1:2], r1:2, [12:23], r12:23
276</p>
277------------------------------
278[1:2], r1:2, [12:23], r12:23
279============================== log: link resolver
280log:@12
281log:trunk
282log:trunk@head
283log:trunk@12
284log:trunk@12:23
285log:trunk@12-23
286log:trunk:12:23
287log:trunk:12-23
288log:trunk@12:head
289log:trunk:12-head
290log:trunk:12@23
291------------------------------
292<p>
293<a class="source" href="/log/?rev=12">log:@12</a>
294<a class="source" href="/log/trunk">log:trunk</a>
295<a class="source" href="/log/trunk?rev=head">log:trunk@head</a>
296<a class="source" href="/log/trunk?rev=12">log:trunk@12</a>
297<a class="source" href="/log/trunk?revs=12-23">log:trunk@12:23</a>
298<a class="source" href="/log/trunk?revs=12-23">log:trunk@12-23</a>
299<a class="source" href="/log/trunk?revs=12-23">log:trunk:12:23</a>
300<a class="source" href="/log/trunk?revs=12-23">log:trunk:12-23</a>
301<a class="source" href="/log/trunk?revs=12-head">log:trunk@12:head</a>
302<a class="source" href="/log/trunk?revs=12-head">log:trunk:12-head</a>
303<a class="missing source" title="No changeset 12@23 in the repository">log:trunk:12@23</a>
304</p>
305------------------------------
306============================== log: link resolver with hash revs and named revs
307log:trac.git@fffffff
308log:trac.git/trunk
309log:trac.git/trunk@HEAD
310log:trac.git/trunk@deadbeef
311log:trac.git/trunk@deadbeef:deadbef1
312log:trac.git/trunk@deadbeef-deadbef1
313log:trac.git/trunk:deadbeef:deadbef1
314log:trac.git/trunk:deadbeef-deadbef1
315log:trac.git/trunk@deadbeef:HEAD
316log:trac.git/trunk:deadbeef-HEAD
317log:trac.git/trunk:deadbeef@deadbef1
318log:trac.git/trunk@1.0-stable
319log:trac.git/trunk@0.12-stable:1.0-stable
320log:trac.git/trunk@v1.0-v1.0.1
321log:trac.git/trunk@0.12-stáblé:1.0-stáblé
322log:trac.git/trunk@vér1.0-vér1.0.1
323------------------------------
324<p>
325<a class="source" href="/log/trac.git/?rev=fffffff">log:trac.git@fffffff</a>
326<a class="source" href="/log/trac.git/trunk">log:trac.git/trunk</a>
327<a class="source" href="/log/trac.git/trunk?rev=HEAD">log:trac.git/trunk@HEAD</a>
328<a class="source" href="/log/trac.git/trunk?rev=deadbeef">log:trac.git/trunk@deadbeef</a>
329<a class="source" href="/log/trac.git/trunk?revs=deadbeef%3Adeadbef1">log:trac.git/trunk@deadbeef:deadbef1</a>
330<a class="source" href="/log/trac.git/trunk?revs=deadbeef%3Adeadbef1">log:trac.git/trunk@deadbeef-deadbef1</a>
331<a class="source" href="/log/trac.git/trunk?revs=deadbeef%3Adeadbef1">log:trac.git/trunk:deadbeef:deadbef1</a>
332<a class="source" href="/log/trac.git/trunk?revs=deadbeef%3Adeadbef1">log:trac.git/trunk:deadbeef-deadbef1</a>
333<a class="source" href="/log/trac.git/trunk?revs=deadbeef%3AHEAD">log:trac.git/trunk@deadbeef:HEAD</a>
334<a class="missing source" title="No changeset deadbeef-HEAD in the repository">log:trac.git/trunk:deadbeef-HEAD</a>
335<a class="missing source" title="No changeset deadbeef@deadbef1 in the repository">log:trac.git/trunk:deadbeef@deadbef1</a>
336<a class="source" href="/log/trac.git/trunk?rev=1.0-stable">log:trac.git/trunk@1.0-stable</a>
337<a class="source" href="/log/trac.git/trunk?revs=0.12-stable%3A1.0-stable">log:trac.git/trunk@0.12-stable:1.0-stable</a>
338<a class="missing source" title="No changeset v1.0-v1.0.1 in the repository">log:trac.git/trunk@v1.0-v1.0.1</a>
339<a class="source" href="/log/trac.git/trunk?revs=0.12-st%C3%A1bl%C3%A9%3A1.0-st%C3%A1bl%C3%A9">log:trac.git/trunk@0.12-stáblé:1.0-stáblé</a>
340<a class="missing source" title="No changeset vér1.0-vér1.0.1 in the repository">log:trac.git/trunk@vér1.0-vér1.0.1</a>
341</p>
342------------------------------
343============================== log: link resolver with missing revisions
344log:@4242
345log:@4242-4243
346log:@notfound
347log:@deadbeef:deadbef0
348log:trunk@4243
349log:trunk@notfound
350[4242:4243]
351------------------------------
352<p>
353<a class="missing source" title="No changeset 4242 in the repository">log:@4242</a>
354<a class="source" href="/log/?revs=4242-4243">log:@4242-4243</a>
355<a class="missing source" title="No changeset notfound in the repository">log:@notfound</a>
356<a class="source" href="/log/?revs=deadbeef-deadbef0">log:@deadbeef:deadbef0</a>
357<a class="missing source" title="No changeset 4243 in the repository">log:trunk@4243</a>
358<a class="missing source" title="No changeset notfound in the repository">log:trunk@notfound</a>
359<a class="source" href="/log/?revs=4242-4243">[4242:4243]</a>
360</p>
361------------------------------
362============================== log: link resolver + query
363log:?limit=10
364log:@12?limit=10
365log:trunk?limit=10
366log:trunk@12?limit=10
367[10:20?verbose=yes&format=changelog]
368[10:20/trunk?verbose=yes&format=changelog]
369------------------------------
370<p>
371<a class="source" href="/log/?limit=10">log:?limit=10</a>
372<a class="source" href="/log/?rev=12&amp;limit=10">log:@12?limit=10</a>
373<a class="source" href="/log/trunk?limit=10">log:trunk?limit=10</a>
374<a class="source" href="/log/trunk?rev=12&amp;limit=10">log:trunk@12?limit=10</a>
375<a class="source" href="/log/?revs=10-20&amp;verbose=yes&amp;format=changelog">[10:20?verbose=yes&amp;format=changelog]</a>
376<a class="source" href="/log/trunk?revs=10-20&amp;verbose=yes&amp;format=changelog">[10:20/trunk?verbose=yes&amp;format=changelog]</a>
377</p>
378------------------------------
379============================== log: link resolver + invalid ranges
380log:@10-20-30
381log:@10,20-30,40-50-60
382log:@10:20:30
383[10-20-30]
384[10:20:30]
385------------------------------
386<p>
387<a class="missing source" title="No changeset 10-20-30 in the repository">log:@10-20-30</a>
388<a class="source" href="/log/?revs=10%2C20-30%2C40-50-60">log:@10,20-30,40-50-60</a>
389<a class="missing source" title="No changeset 10:20:30 in the repository">log:@10:20:30</a>
390[10-20-30]
391[10:20:30]
392</p>
393------------------------------
394============================== Multiple Log ranges
395r12:20,25,35:56,68,69,100-120
396[12:20,25,35:56,68,69,100-120]
397[12:20,25,88:head,68,69] (not supported)
398------------------------------
399<p>
400<a class="source" href="/log/?revs=12-20%2C25%2C35-56%2C68-69%2C100-120">r12:20,25,35:56,68,69,100-120</a>
401<a class="source" href="/log/?revs=12-20%2C25%2C35-56%2C68-69%2C100-120">[12:20,25,35:56,68,69,100-120]</a>
402[12:20,25,88:head,68,69] (not supported)
403</p>
404------------------------------
405============================== Link resolver counter examples
406rfc:4180 should not be a log link
407------------------------------
408<p>
409rfc:4180 should not be a log link
410</p>
411------------------------------
412============================== Log range InterTrac shorthands
413[T3317:3318]
414[trac 3317:3318]
415[trac 3317:3318/trunk]
416------------------------------
417<p>
418<a class="ext-link" href="https://trac.edgewall.org/intertrac/log%3A/%403317%3A3318" title="log:/@3317:3318 in The Trac Project"><span class="icon"></span>[T3317:3318]</a>
419<a class="ext-link" href="https://trac.edgewall.org/intertrac/log%3A/%403317%3A3318" title="log:/@3317:3318 in The Trac Project"><span class="icon"></span>[trac 3317:3318]</a>
420<a class="ext-link" href="https://trac.edgewall.org/intertrac/log%3A/trunk%403317%3A3318" title="log:/trunk@3317:3318 in The Trac Project"><span class="icon"></span>[trac 3317:3318/trunk]</a>
421</p>
422------------------------------
423============================== Log range with unicode digits
424r₁₂:₂₀,₂₅,₃₀-₃₅
425[₁₂:₂₀,₂₅,₃₀-₃₅]
426[T₃₃₁₇:₃₃₁₈]
427[trac ₃₃₁₇:₃₃₁₈]
428------------------------------
429<p>
430r₁₂:₂₀,₂₅,₃₀-₃₅
431[₁₂:₂₀,₂₅,₃₀-₃₅]
432[T₃₃₁₇:₃₃₁₈]
433[trac ₃₃₁₇:₃₃₁₈]
434</p>
435------------------------------
436"""
437
438
439DIFF_TEST_CASES = """
440============================== diff: link resolver
441diff:trunk//branch
442diff:trunk@12//branch@23
443diff:trunk@12:23
444diff:@12:23
445------------------------------
446<p>
447<a class="changeset" href="/changeset?new_path=branch&amp;old_path=trunk" title="Diff from trunk@latest to branch@latest">diff:trunk//branch</a>
448<a class="changeset" href="/changeset?new=23&amp;new_path=branch&amp;old=12&amp;old_path=trunk" title="Diff from trunk@12 to branch@23">diff:trunk@12//branch@23</a>
449<a class="changeset" href="/changeset?new=23&amp;new_path=trunk&amp;old=12&amp;old_path=trunk" title="Diff [12:23] for trunk">diff:trunk@12:23</a>
450<a class="changeset" href="/changeset?new=23&amp;old=12" title="Diff [12:23] for /">diff:@12:23</a>
451</p>
452------------------------------
453============================== diff: link resolver + query
454diff:trunk//branch?format=diff
455------------------------------
456<p>
457<a class="changeset" href="/changeset?new_path=branch&amp;old_path=trunk&amp;format=diff" title="Diff from trunk@latest to branch@latest">diff:trunk//branch?format=diff</a>
458</p>
459------------------------------
460============================== diff: link, empty diff
461diff://
462------------------------------
463<p>
464<a class="changeset" title="Diff [latest:latest] for /">diff://</a>
465</p>
466------------------------------
467"""
468
469
470SOURCE_TEST_CASES = """
471============================== source: link resolver
472source:/foo/bar
473source:/foo/bar#42   # no long works as rev spec
474source:/foo/bar#head #
475source:/foo/bar@42
476source:/foo/bar@head
477source:/foo%20bar/baz%2Bquux
478source:@42
479source:/foo/bar@42#L20
480source:/foo/bar@head#L20
481source:/foo/bar@#L20
482source:/missing/file
483------------------------------
484<p>
485<a class="source" href="/browser/foo/bar">source:/foo/bar</a><a class="trac-rawlink" href="/export/HEAD/foo/bar" title="Download"></a>
486<a class="source" href="/browser/foo/bar#42">source:/foo/bar#42</a><a class="trac-rawlink" href="/export/HEAD/foo/bar#42" title="Download"></a>   # no long works as rev spec
487<a class="source" href="/browser/foo/bar#head">source:/foo/bar#head</a><a class="trac-rawlink" href="/export/HEAD/foo/bar#head" title="Download"></a> #
488<a class="source" href="/browser/foo/bar?rev=42">source:/foo/bar@42</a><a class="trac-rawlink" href="/export/42/foo/bar" title="Download"></a>
489<a class="source" href="/browser/foo/bar?rev=head">source:/foo/bar@head</a><a class="trac-rawlink" href="/export/head/foo/bar" title="Download"></a>
490<a class="source" href="/browser/foo%2520bar/baz%252Bquux">source:/foo%20bar/baz%2Bquux</a><a class="trac-rawlink" href="/export/HEAD/foo%2520bar/baz%252Bquux" title="Download"></a>
491<a class="source" href="/browser/?rev=42">source:@42</a><a class="trac-rawlink" href="/export/42/" title="Download"></a>
492<a class="source" href="/browser/foo/bar?rev=42#L20">source:/foo/bar@42#L20</a><a class="trac-rawlink" href="/export/42/foo/bar#L20" title="Download"></a>
493<a class="source" href="/browser/foo/bar?rev=head#L20">source:/foo/bar@head#L20</a><a class="trac-rawlink" href="/export/head/foo/bar#L20" title="Download"></a>
494<a class="source" href="/browser/foo/bar#L20">source:/foo/bar@#L20</a><a class="trac-rawlink" href="/export/HEAD/foo/bar#L20" title="Download"></a>
495<a class="missing source">source:/missing/file</a>
496</p>
497------------------------------
498============================== source: link resolver + query
499source:/foo?order=size&desc=1
500source:/foo/bar?format=raw
501------------------------------
502<p>
503<a class="source" href="/browser/foo?order=size&amp;desc=1">source:/foo?order=size&amp;desc=1</a>
504<a class="source" href="/browser/foo/bar?format=raw">source:/foo/bar?format=raw</a><a class="trac-rawlink" href="/export/HEAD/foo/bar" title="Download"></a>
505</p>
506------------------------------
507============================== source: provider, with quoting
508source:'even with whitespaces'
509source:"even with whitespaces"
510[source:'even with whitespaces' Path with spaces]
511[source:"even with whitespaces" Path with spaces]
512------------------------------
513<p>
514<a class="source" href="/browser/even%20with%20whitespaces">source:'even with whitespaces'</a><a class="trac-rawlink" href="/export/HEAD/even%20with%20whitespaces" title="Download"></a>
515<a class="source" href="/browser/even%20with%20whitespaces">source:"even with whitespaces"</a><a class="trac-rawlink" href="/export/HEAD/even%20with%20whitespaces" title="Download"></a>
516<a class="source" href="/browser/even%20with%20whitespaces">Path with spaces</a><a class="trac-rawlink" href="/export/HEAD/even%20with%20whitespaces" title="Download"></a>
517<a class="source" href="/browser/even%20with%20whitespaces">Path with spaces</a><a class="trac-rawlink" href="/export/HEAD/even%20with%20whitespaces" title="Download"></a>
518</p>
519------------------------------
520============================== export: link resolver
521export:/foo/bar.html
522export:123:/foo/pict.gif
523export:/foo/pict.gif@123
524------------------------------
525<p>
526<a class="export" href="/export/HEAD/foo/bar.html" title="Download">export:/foo/bar.html</a>
527<a class="export" href="/export/123/foo/pict.gif" title="Download">export:123:/foo/pict.gif</a>
528<a class="export" href="/export/123/foo/pict.gif" title="Download">export:/foo/pict.gif@123</a>
529</p>
530------------------------------
531============================== export: link resolver + fragment
532export:/foo/bar.html#header
533------------------------------
534<p>
535<a class="export" href="/export/HEAD/foo/bar.html#header" title="Download">export:/foo/bar.html#header</a>
536</p>
537------------------------------
538""" # " (be Emacs friendly...)
539
540
541def test_suite():
542    suite = unittest.TestSuite()
543    suite.addTest(formatter.test_suite(CHANGESET_TEST_CASES, repository_setup,
544                                       __file__))
545    suite.addTest(formatter.test_suite(LOG_TEST_CASES, repository_setup,
546                                       __file__))
547    suite.addTest(formatter.test_suite(DIFF_TEST_CASES, file=__file__))
548    suite.addTest(formatter.test_suite(SOURCE_TEST_CASES, repository_setup,
549                                       __file__))
550    return suite
551
552
553if __name__ == '__main__':
554    unittest.main(defaultTest='test_suite')
555