##// END OF EJS Templates
vcs: remove unused code and test of it...
domruf -
r6591:986efdcf default
parent child Browse files
Show More
@@ -1,635 +1,625 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 vcs.nodes
4 4 ~~~~~~~~~
5 5
6 6 Module holding everything related to vcs nodes.
7 7
8 8 :created_on: Apr 8, 2010
9 9 :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
10 10 """
11 11
12 12 import stat
13 13 import posixpath
14 14 import mimetypes
15 15
16 16 from kallithea.lib.vcs.backends.base import EmptyChangeset
17 17 from kallithea.lib.vcs.exceptions import NodeError, RemovedFileNodeError
18 18 from kallithea.lib.vcs.utils.lazy import LazyProperty
19 19 from kallithea.lib.vcs.utils import safe_unicode, safe_str
20 20
21 21
22 22 class NodeKind:
23 23 SUBMODULE = -1
24 24 DIR = 1
25 25 FILE = 2
26 26
27 27
28 28 class NodeState:
29 29 ADDED = u'added'
30 30 CHANGED = u'changed'
31 31 NOT_CHANGED = u'not changed'
32 32 REMOVED = u'removed'
33 33
34 34
35 35 class NodeGeneratorBase(object):
36 36 """
37 37 Base class for removed added and changed filenodes, it's a lazy generator
38 38 class that will create filenodes only on iteration or call
39 39
40 40 The len method doesn't need to create filenodes at all
41 41 """
42 42
43 43 def __init__(self, current_paths, cs):
44 44 self.cs = cs
45 45 self.current_paths = current_paths
46 46
47 47 def __call__(self):
48 48 return [n for n in self]
49 49
50 50 def __getslice__(self, i, j):
51 51 for p in self.current_paths[i:j]:
52 52 yield self.cs.get_node(p)
53 53
54 54 def __len__(self):
55 55 return len(self.current_paths)
56 56
57 57 def __iter__(self):
58 58 for p in self.current_paths:
59 59 yield self.cs.get_node(p)
60 60
61 61
62 62 class AddedFileNodesGenerator(NodeGeneratorBase):
63 63 """
64 64 Class holding Added files for current changeset
65 65 """
66 66 pass
67 67
68 68
69 69 class ChangedFileNodesGenerator(NodeGeneratorBase):
70 70 """
71 71 Class holding Changed files for current changeset
72 72 """
73 73 pass
74 74
75 75
76 76 class RemovedFileNodesGenerator(NodeGeneratorBase):
77 77 """
78 78 Class holding removed files for current changeset
79 79 """
80 80 def __iter__(self):
81 81 for p in self.current_paths:
82 82 yield RemovedFileNode(path=p)
83 83
84 84 def __getslice__(self, i, j):
85 85 for p in self.current_paths[i:j]:
86 86 yield RemovedFileNode(path=p)
87 87
88 88
89 89 class Node(object):
90 90 """
91 91 Simplest class representing file or directory on repository. SCM backends
92 92 should use ``FileNode`` and ``DirNode`` subclasses rather than ``Node``
93 93 directly.
94 94
95 95 Node's ``path`` cannot start with slash as we operate on *relative* paths
96 96 only. Moreover, every single node is identified by the ``path`` attribute,
97 97 so it cannot end with slash, too. Otherwise, path could lead to mistakes.
98 98 """
99 99
100 100 def __init__(self, path, kind):
101 101 if path.startswith('/'):
102 102 raise NodeError("Cannot initialize Node objects with slash at "
103 103 "the beginning as only relative paths are supported")
104 104 self.path = safe_str(path.rstrip('/')) # we store paths as str
105 105 if path == '' and kind != NodeKind.DIR:
106 106 raise NodeError("Only DirNode and its subclasses may be "
107 107 "initialized with empty path")
108 108 self.kind = kind
109 109 #self.dirs, self.files = [], []
110 110 if self.is_root() and not self.is_dir():
111 111 raise NodeError("Root node cannot be FILE kind")
112 112
113 113 @LazyProperty
114 114 def parent(self):
115 115 parent_path = self.get_parent_path()
116 116 if parent_path:
117 117 if self.changeset:
118 118 return self.changeset.get_node(parent_path)
119 119 return DirNode(parent_path)
120 120 return None
121 121
122 122 @LazyProperty
123 123 def unicode_path(self):
124 124 return safe_unicode(self.path)
125 125
126 126 @LazyProperty
127 127 def name(self):
128 128 """
129 129 Returns name of the node so if its path
130 130 then only last part is returned.
131 131 """
132 132 return safe_unicode(self.path.rstrip('/').split('/')[-1])
133 133
134 134 def _get_kind(self):
135 135 return self._kind
136 136
137 137 def _set_kind(self, kind):
138 138 if hasattr(self, '_kind'):
139 139 raise NodeError("Cannot change node's kind")
140 140 else:
141 141 self._kind = kind
142 142 # Post setter check (path's trailing slash)
143 143 if self.path.endswith('/'):
144 144 raise NodeError("Node's path cannot end with slash")
145 145
146 146 kind = property(_get_kind, _set_kind)
147 147
148 148 def __cmp__(self, other):
149 149 """
150 150 Comparator using name of the node, needed for quick list sorting.
151 151 """
152 152 kind_cmp = cmp(self.kind, other.kind)
153 153 if kind_cmp:
154 154 return kind_cmp
155 155 return cmp(self.name, other.name)
156 156
157 157 def __eq__(self, other):
158 158 for attr in ['name', 'path', 'kind']:
159 159 if getattr(self, attr) != getattr(other, attr):
160 160 return False
161 161 if self.is_file():
162 162 if self.content != other.content:
163 163 return False
164 164 else:
165 165 # For DirNode's check without entering each dir
166 166 self_nodes_paths = list(sorted(n.path for n in self.nodes))
167 167 other_nodes_paths = list(sorted(n.path for n in self.nodes))
168 168 if self_nodes_paths != other_nodes_paths:
169 169 return False
170 170 return True
171 171
172 172 def __nq__(self, other):
173 173 return not self.__eq__(other)
174 174
175 175 def __repr__(self):
176 176 return '<%s %r>' % (self.__class__.__name__, self.path)
177 177
178 178 def __str__(self):
179 179 return self.__repr__()
180 180
181 181 def __unicode__(self):
182 182 return self.name
183 183
184 184 def get_parent_path(self):
185 185 """
186 186 Returns node's parent path or empty string if node is root.
187 187 """
188 188 if self.is_root():
189 189 return ''
190 190 return posixpath.dirname(self.path.rstrip('/')) + '/'
191 191
192 192 def is_file(self):
193 193 """
194 194 Returns ``True`` if node's kind is ``NodeKind.FILE``, ``False``
195 195 otherwise.
196 196 """
197 197 return self.kind == NodeKind.FILE
198 198
199 199 def is_dir(self):
200 200 """
201 201 Returns ``True`` if node's kind is ``NodeKind.DIR``, ``False``
202 202 otherwise.
203 203 """
204 204 return self.kind == NodeKind.DIR
205 205
206 206 def is_root(self):
207 207 """
208 208 Returns ``True`` if node is a root node and ``False`` otherwise.
209 209 """
210 210 return self.kind == NodeKind.DIR and self.path == ''
211 211
212 212 def is_submodule(self):
213 213 """
214 214 Returns ``True`` if node's kind is ``NodeKind.SUBMODULE``, ``False``
215 215 otherwise.
216 216 """
217 217 return self.kind == NodeKind.SUBMODULE
218 218
219 219 @LazyProperty
220 220 def added(self):
221 221 return self.state is NodeState.ADDED
222 222
223 223 @LazyProperty
224 224 def changed(self):
225 225 return self.state is NodeState.CHANGED
226 226
227 227 @LazyProperty
228 228 def not_changed(self):
229 229 return self.state is NodeState.NOT_CHANGED
230 230
231 231 @LazyProperty
232 232 def removed(self):
233 233 return self.state is NodeState.REMOVED
234 234
235 235
236 236 class FileNode(Node):
237 237 """
238 238 Class representing file nodes.
239 239
240 240 :attribute: path: path to the node, relative to repository's root
241 241 :attribute: content: if given arbitrary sets content of the file
242 242 :attribute: changeset: if given, first time content is accessed, callback
243 243 :attribute: mode: octal stat mode for a node. Default is 0100644.
244 244 """
245 245
246 246 def __init__(self, path, content=None, changeset=None, mode=None):
247 247 """
248 248 Only one of ``content`` and ``changeset`` may be given. Passing both
249 249 would raise ``NodeError`` exception.
250 250
251 251 :param path: relative path to the node
252 252 :param content: content may be passed to constructor
253 253 :param changeset: if given, will use it to lazily fetch content
254 254 :param mode: octal representation of ST_MODE (i.e. 0100644)
255 255 """
256 256
257 257 if content and changeset:
258 258 raise NodeError("Cannot use both content and changeset")
259 259 super(FileNode, self).__init__(path, kind=NodeKind.FILE)
260 260 self.changeset = changeset
261 261 self._content = content
262 262 self._mode = mode or 0100644
263 263
264 264 @LazyProperty
265 265 def mode(self):
266 266 """
267 267 Returns lazily mode of the FileNode. If ``changeset`` is not set, would
268 268 use value given at initialization or 0100644 (default).
269 269 """
270 270 if self.changeset:
271 271 mode = self.changeset.get_file_mode(self.path)
272 272 else:
273 273 mode = self._mode
274 274 return mode
275 275
276 276 def _get_content(self):
277 277 if self.changeset:
278 278 content = self.changeset.get_file_content(self.path)
279 279 else:
280 280 content = self._content
281 281 return content
282 282
283 283 @property
284 284 def content(self):
285 285 """
286 286 Returns lazily content of the FileNode. If possible, would try to
287 287 decode content from UTF-8.
288 288 """
289 289 content = self._get_content()
290 290
291 291 if bool(content and '\0' in content):
292 292 return content
293 293 return safe_unicode(content)
294 294
295 295 @LazyProperty
296 296 def size(self):
297 297 if self.changeset:
298 298 return self.changeset.get_file_size(self.path)
299 299 raise NodeError("Cannot retrieve size of the file without related "
300 300 "changeset attribute")
301 301
302 302 @LazyProperty
303 303 def message(self):
304 304 if self.changeset:
305 305 return self.last_changeset.message
306 306 raise NodeError("Cannot retrieve message of the file without related "
307 307 "changeset attribute")
308 308
309 309 @LazyProperty
310 310 def last_changeset(self):
311 311 if self.changeset:
312 312 return self.changeset.get_file_changeset(self.path)
313 313 raise NodeError("Cannot retrieve last changeset of the file without "
314 314 "related changeset attribute")
315 315
316 316 def get_mimetype(self):
317 317 """
318 Mimetype is calculated based on the file's content. If ``_mimetype``
319 attribute is available, it will be returned (backends which store
320 mimetypes or can easily recognize them, should set this private
321 attribute to indicate that type should *NOT* be calculated).
318 Mimetype is calculated based on the file's content.
322 319 """
323 if hasattr(self, '_mimetype'):
324 if (isinstance(self._mimetype, (tuple, list,)) and
325 len(self._mimetype) == 2):
326 return self._mimetype
327 else:
328 raise NodeError('given _mimetype attribute must be an 2 '
329 'element list or tuple')
330 320
331 321 mtype, encoding = mimetypes.guess_type(self.name)
332 322
333 323 if mtype is None:
334 324 if self.is_binary:
335 325 mtype = 'application/octet-stream'
336 326 encoding = None
337 327 else:
338 328 mtype = 'text/plain'
339 329 encoding = None
340 330
341 331 #try with pygments
342 332 try:
343 333 from pygments import lexers
344 334 mt = lexers.get_lexer_for_filename(self.name).mimetypes
345 335 except lexers.ClassNotFound:
346 336 mt = None
347 337
348 338 if mt:
349 339 mtype = mt[0]
350 340
351 341 return mtype, encoding
352 342
353 343 @LazyProperty
354 344 def mimetype(self):
355 345 """
356 346 Wrapper around full mimetype info. It returns only type of fetched
357 347 mimetype without the encoding part. use get_mimetype function to fetch
358 348 full set of (type,encoding)
359 349 """
360 350 return self.get_mimetype()[0]
361 351
362 352 @LazyProperty
363 353 def mimetype_main(self):
364 354 return self.mimetype.split('/')[0]
365 355
366 356 @LazyProperty
367 357 def lexer(self):
368 358 """
369 359 Returns pygment's lexer class. Would try to guess lexer taking file's
370 360 content, name and mimetype.
371 361 """
372 362 from pygments import lexers
373 363 try:
374 364 lexer = lexers.guess_lexer_for_filename(self.name, self.content, stripnl=False)
375 365 except lexers.ClassNotFound:
376 366 lexer = lexers.TextLexer(stripnl=False)
377 367 # returns first alias
378 368 return lexer
379 369
380 370 @LazyProperty
381 371 def lexer_alias(self):
382 372 """
383 373 Returns first alias of the lexer guessed for this file.
384 374 """
385 375 return self.lexer.aliases[0]
386 376
387 377 @LazyProperty
388 378 def history(self):
389 379 """
390 380 Returns a list of changeset for this file in which the file was changed
391 381 """
392 382 if self.changeset is None:
393 383 raise NodeError('Unable to get changeset for this FileNode')
394 384 return self.changeset.get_file_history(self.path)
395 385
396 386 @LazyProperty
397 387 def annotate(self):
398 388 """
399 389 Returns a list of three element tuples with lineno,changeset and line
400 390 """
401 391 if self.changeset is None:
402 392 raise NodeError('Unable to get changeset for this FileNode')
403 393 return self.changeset.get_file_annotate(self.path)
404 394
405 395 @LazyProperty
406 396 def state(self):
407 397 if not self.changeset:
408 398 raise NodeError("Cannot check state of the node if it's not "
409 399 "linked with changeset")
410 400 elif self.path in (node.path for node in self.changeset.added):
411 401 return NodeState.ADDED
412 402 elif self.path in (node.path for node in self.changeset.changed):
413 403 return NodeState.CHANGED
414 404 else:
415 405 return NodeState.NOT_CHANGED
416 406
417 407 @property
418 408 def is_binary(self):
419 409 """
420 410 Returns True if file has binary content.
421 411 """
422 412 _bin = '\0' in self._get_content()
423 413 return _bin
424 414
425 415 def is_browser_compatible_image(self):
426 416 return self.mimetype in [
427 417 "image/gif",
428 418 "image/jpeg",
429 419 "image/png",
430 420 "image/bmp"
431 421 ]
432 422
433 423 @LazyProperty
434 424 def extension(self):
435 425 """Returns filenode extension"""
436 426 return self.name.split('.')[-1]
437 427
438 428 @property
439 429 def is_executable(self):
440 430 """
441 431 Returns ``True`` if file has executable flag turned on.
442 432 """
443 433 return bool(self.mode & stat.S_IXUSR)
444 434
445 435 def __repr__(self):
446 436 return '<%s %r @ %s>' % (self.__class__.__name__, self.path,
447 437 getattr(self.changeset, 'short_id', ''))
448 438
449 439
450 440 class RemovedFileNode(FileNode):
451 441 """
452 442 Dummy FileNode class - trying to access any public attribute except path,
453 443 name, kind or state (or methods/attributes checking those two) would raise
454 444 RemovedFileNodeError.
455 445 """
456 446 ALLOWED_ATTRIBUTES = [
457 447 'name', 'path', 'state', 'is_root', 'is_file', 'is_dir', 'kind',
458 448 'added', 'changed', 'not_changed', 'removed'
459 449 ]
460 450
461 451 def __init__(self, path):
462 452 """
463 453 :param path: relative path to the node
464 454 """
465 455 super(RemovedFileNode, self).__init__(path=path)
466 456
467 457 def __getattribute__(self, attr):
468 458 if attr.startswith('_') or attr in RemovedFileNode.ALLOWED_ATTRIBUTES:
469 459 return super(RemovedFileNode, self).__getattribute__(attr)
470 460 raise RemovedFileNodeError("Cannot access attribute %s on "
471 461 "RemovedFileNode" % attr)
472 462
473 463 @LazyProperty
474 464 def state(self):
475 465 return NodeState.REMOVED
476 466
477 467
478 468 class DirNode(Node):
479 469 """
480 470 DirNode stores list of files and directories within this node.
481 471 Nodes may be used standalone but within repository context they
482 472 lazily fetch data within same repository's changeset.
483 473 """
484 474
485 475 def __init__(self, path, nodes=(), changeset=None):
486 476 """
487 477 Only one of ``nodes`` and ``changeset`` may be given. Passing both
488 478 would raise ``NodeError`` exception.
489 479
490 480 :param path: relative path to the node
491 481 :param nodes: content may be passed to constructor
492 482 :param changeset: if given, will use it to lazily fetch content
493 483 :param size: always 0 for ``DirNode``
494 484 """
495 485 if nodes and changeset:
496 486 raise NodeError("Cannot use both nodes and changeset")
497 487 super(DirNode, self).__init__(path, NodeKind.DIR)
498 488 self.changeset = changeset
499 489 self._nodes = nodes
500 490
501 491 @LazyProperty
502 492 def content(self):
503 493 raise NodeError("%s represents a dir and has no ``content`` attribute"
504 494 % self)
505 495
506 496 @LazyProperty
507 497 def nodes(self):
508 498 if self.changeset:
509 499 nodes = self.changeset.get_nodes(self.path)
510 500 else:
511 501 nodes = self._nodes
512 502 self._nodes_dict = dict((node.path, node) for node in nodes)
513 503 return sorted(nodes)
514 504
515 505 @LazyProperty
516 506 def files(self):
517 507 return sorted((node for node in self.nodes if node.is_file()))
518 508
519 509 @LazyProperty
520 510 def dirs(self):
521 511 return sorted((node for node in self.nodes if node.is_dir()))
522 512
523 513 def __iter__(self):
524 514 for node in self.nodes:
525 515 yield node
526 516
527 517 def get_node(self, path):
528 518 """
529 519 Returns node from within this particular ``DirNode``, so it is now
530 520 allowed to fetch, i.e. node located at 'docs/api/index.rst' from node
531 521 'docs'. In order to access deeper nodes one must fetch nodes between
532 522 them first - this would work::
533 523
534 524 docs = root.get_node('docs')
535 525 docs.get_node('api').get_node('index.rst')
536 526
537 527 :param: path - relative to the current node
538 528
539 529 .. note::
540 530 To access lazily (as in example above) node have to be initialized
541 531 with related changeset object - without it node is out of
542 532 context and may know nothing about anything else than nearest
543 533 (located at same level) nodes.
544 534 """
545 535 try:
546 536 path = path.rstrip('/')
547 537 if path == '':
548 538 raise NodeError("Cannot retrieve node without path")
549 539 self.nodes # access nodes first in order to set _nodes_dict
550 540 paths = path.split('/')
551 541 if len(paths) == 1:
552 542 if not self.is_root():
553 543 path = '/'.join((self.path, paths[0]))
554 544 else:
555 545 path = paths[0]
556 546 return self._nodes_dict[path]
557 547 elif len(paths) > 1:
558 548 if self.changeset is None:
559 549 raise NodeError("Cannot access deeper "
560 550 "nodes without changeset")
561 551 else:
562 552 path1, path2 = paths[0], '/'.join(paths[1:])
563 553 return self.get_node(path1).get_node(path2)
564 554 else:
565 555 raise KeyError
566 556 except KeyError:
567 557 raise NodeError("Node does not exist at %s" % path)
568 558
569 559 @LazyProperty
570 560 def state(self):
571 561 raise NodeError("Cannot access state of DirNode")
572 562
573 563 @LazyProperty
574 564 def size(self):
575 565 size = 0
576 566 for root, dirs, files in self.changeset.walk(self.path):
577 567 for f in files:
578 568 size += f.size
579 569
580 570 return size
581 571
582 572 def __repr__(self):
583 573 return '<%s %r @ %s>' % (self.__class__.__name__, self.path,
584 574 getattr(self.changeset, 'short_id', ''))
585 575
586 576
587 577 class RootNode(DirNode):
588 578 """
589 579 DirNode being the root node of the repository.
590 580 """
591 581
592 582 def __init__(self, nodes=(), changeset=None):
593 583 super(RootNode, self).__init__(path='', nodes=nodes,
594 584 changeset=changeset)
595 585
596 586 def __repr__(self):
597 587 return '<%s>' % self.__class__.__name__
598 588
599 589
600 590 class SubModuleNode(Node):
601 591 """
602 592 represents a SubModule of Git or SubRepo of Mercurial
603 593 """
604 594 is_binary = False
605 595 size = 0
606 596
607 597 def __init__(self, name, url=None, changeset=None, alias=None):
608 598 self.path = name
609 599 self.kind = NodeKind.SUBMODULE
610 600 self.alias = alias
611 601 # we have to use emptyChangeset here since this can point to svn/git/hg
612 602 # submodules we cannot get from repository
613 603 self.changeset = EmptyChangeset(str(changeset), alias=alias)
614 604 self.url = url or self._extract_submodule_url()
615 605
616 606 def __repr__(self):
617 607 return '<%s %r @ %s>' % (self.__class__.__name__, self.path,
618 608 getattr(self.changeset, 'short_id', ''))
619 609
620 610 def _extract_submodule_url(self):
621 611 if self.alias == 'git':
622 612 #TODO: find a way to parse gits submodule file and extract the
623 613 # linking URL
624 614 return self.path
625 615 if self.alias == 'hg':
626 616 return self.path
627 617
628 618 @LazyProperty
629 619 def name(self):
630 620 """
631 621 Returns name of the node so if its path
632 622 then only last part is returned.
633 623 """
634 624 org = safe_unicode(self.path.rstrip('/').split('/')[-1])
635 625 return u'%s @ %s' % (org, self.changeset.short_id)
@@ -1,183 +1,182 b''
1 1
2 2 import stat
3 3 import mimetypes
4 4 from kallithea.lib.vcs.nodes import DirNode
5 5 from kallithea.lib.vcs.nodes import FileNode
6 6 from kallithea.lib.vcs.nodes import Node
7 7 from kallithea.lib.vcs.nodes import NodeError
8 8 from kallithea.lib.vcs.nodes import NodeKind
9 9 from kallithea.lib.vcs.utils.compat import unittest
10 10
11 11
12 12 class NodeBasicTest(unittest.TestCase):
13 13
14 14 def test_init(self):
15 15 """
16 16 Cannot initialize Node objects with path with slash at the beginning.
17 17 """
18 18 wrong_paths = (
19 19 '/foo',
20 20 '/foo/bar'
21 21 )
22 22 for path in wrong_paths:
23 23 self.assertRaises(NodeError, Node, path, NodeKind.FILE)
24 24
25 25 wrong_paths = (
26 26 '/foo/',
27 27 '/foo/bar/'
28 28 )
29 29 for path in wrong_paths:
30 30 self.assertRaises(NodeError, Node, path, NodeKind.DIR)
31 31
32 32 def test_name(self):
33 33 node = Node('', NodeKind.DIR)
34 34 self.assertEqual(node.name, '')
35 35
36 36 node = Node('path', NodeKind.FILE)
37 37 self.assertEqual(node.name, 'path')
38 38
39 39 node = Node('path/', NodeKind.DIR)
40 40 self.assertEqual(node.name, 'path')
41 41
42 42 node = Node('some/path', NodeKind.FILE)
43 43 self.assertEqual(node.name, 'path')
44 44
45 45 node = Node('some/path/', NodeKind.DIR)
46 46 self.assertEqual(node.name, 'path')
47 47
48 48 def test_root_node(self):
49 49 self.assertRaises(NodeError, Node, '', NodeKind.FILE)
50 50
51 51 def test_kind_setter(self):
52 52 node = Node('', NodeKind.DIR)
53 53 self.assertRaises(NodeError, setattr, node, 'kind', NodeKind.FILE)
54 54
55 55 def _test_parent_path(self, node_path, expected_parent_path):
56 56 """
57 57 Tests if node's parent path are properly computed.
58 58 """
59 59 node = Node(node_path, NodeKind.DIR)
60 60 parent_path = node.get_parent_path()
61 61 self.assertTrue(parent_path.endswith('/') or \
62 62 node.is_root() and parent_path == '')
63 63 self.assertEqual(parent_path, expected_parent_path,
64 64 "Node's path is %r and parent path is %r but should be %r"
65 65 % (node.path, parent_path, expected_parent_path))
66 66
67 67 def test_parent_path(self):
68 68 test_paths = (
69 69 # (node_path, expected_parent_path)
70 70 ('', ''),
71 71 ('some/path/', 'some/'),
72 72 ('some/longer/path/', 'some/longer/'),
73 73 )
74 74 for node_path, expected_parent_path in test_paths:
75 75 self._test_parent_path(node_path, expected_parent_path)
76 76
77 77 '''
78 78 def _test_trailing_slash(self, path):
79 79 if not path.endswith('/'):
80 80 pytest.fail("Trailing slash tests needs paths to end with slash")
81 81 for kind in NodeKind.FILE, NodeKind.DIR:
82 82 self.assertRaises(NodeError, Node, path=path, kind=kind)
83 83
84 84 def test_trailing_slash(self):
85 85 for path in ('/', 'foo/', 'foo/bar/', 'foo/bar/biz/'):
86 86 self._test_trailing_slash(path)
87 87 '''
88 88
89 89 def test_is_file(self):
90 90 node = Node('any', NodeKind.FILE)
91 91 self.assertTrue(node.is_file())
92 92
93 93 node = FileNode('any')
94 94 self.assertTrue(node.is_file())
95 95 self.assertRaises(AttributeError, getattr, node, 'nodes')
96 96
97 97 def test_is_dir(self):
98 98 node = Node('any_dir', NodeKind.DIR)
99 99 self.assertTrue(node.is_dir())
100 100
101 101 node = DirNode('any_dir')
102 102
103 103 self.assertTrue(node.is_dir())
104 104 self.assertRaises(NodeError, getattr, node, 'content')
105 105
106 106 def test_dir_node_iter(self):
107 107 nodes = [
108 108 DirNode('docs'),
109 109 DirNode('tests'),
110 110 FileNode('bar'),
111 111 FileNode('foo'),
112 112 FileNode('readme.txt'),
113 113 FileNode('setup.py'),
114 114 ]
115 115 dirnode = DirNode('', nodes=nodes)
116 116 for node in dirnode:
117 117 node == dirnode.get_node(node.path)
118 118
119 119 def test_node_state(self):
120 120 """
121 121 Without link to changeset nodes should raise NodeError.
122 122 """
123 123 node = FileNode('anything')
124 124 self.assertRaises(NodeError, getattr, node, 'state')
125 125 node = DirNode('anything')
126 126 self.assertRaises(NodeError, getattr, node, 'state')
127 127
128 128 def test_file_node_stat(self):
129 129 node = FileNode('foobar', 'empty... almost')
130 130 mode = node.mode # default should be 0100644
131 131 self.assertTrue(mode & stat.S_IRUSR)
132 132 self.assertTrue(mode & stat.S_IWUSR)
133 133 self.assertTrue(mode & stat.S_IRGRP)
134 134 self.assertTrue(mode & stat.S_IROTH)
135 135 self.assertFalse(mode & stat.S_IWGRP)
136 136 self.assertFalse(mode & stat.S_IWOTH)
137 137 self.assertFalse(mode & stat.S_IXUSR)
138 138 self.assertFalse(mode & stat.S_IXGRP)
139 139 self.assertFalse(mode & stat.S_IXOTH)
140 140
141 141 def test_file_node_is_executable(self):
142 142 node = FileNode('foobar', 'empty... almost', mode=0100755)
143 143 self.assertTrue(node.is_executable)
144 144
145 145 node = FileNode('foobar', 'empty... almost', mode=0100500)
146 146 self.assertTrue(node.is_executable)
147 147
148 148 node = FileNode('foobar', 'empty... almost', mode=0100644)
149 149 self.assertFalse(node.is_executable)
150 150
151 151 def test_mimetype(self):
152 152 py_node = FileNode('test.py')
153 153 tar_node = FileNode('test.tar.gz')
154 154
155 ext = 'CustomExtension'
156
157 155 my_node2 = FileNode('myfile2')
158 my_node2._mimetype = [ext]
156 my_node2._content = 'foobar'
159 157
160 158 my_node3 = FileNode('myfile3')
161 my_node3._mimetype = [ext,ext]
159 my_node3._content = '\0foobar'
162 160
163 161 self.assertEqual(py_node.mimetype, mimetypes.guess_type(py_node.name)[0])
164 162 self.assertEqual(py_node.get_mimetype(), mimetypes.guess_type(py_node.name))
165 163
166 164 self.assertEqual(tar_node.mimetype, mimetypes.guess_type(tar_node.name)[0])
167 165 self.assertEqual(tar_node.get_mimetype(), mimetypes.guess_type(tar_node.name))
168 166
169 self.assertRaises(NodeError,my_node2.get_mimetype)
167 self.assertEqual(my_node2.mimetype, 'text/plain')
168 self.assertEqual(my_node2.get_mimetype(), ('text/plain', None))
170 169
171 self.assertEqual(my_node3.mimetype,ext)
172 self.assertEqual(my_node3.get_mimetype(),[ext,ext])
170 self.assertEqual(my_node3.mimetype, 'application/octet-stream')
171 self.assertEqual(my_node3.get_mimetype(), ('application/octet-stream', None))
173 172
174 173 class NodeContentTest(unittest.TestCase):
175 174
176 175 def test_if_binary(self):
177 176 data = """\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f??a\x00\x00\x00\x04gAMA\x00\x00\xaf?7\x05\x8a?\x00\x00\x00\x19tEXtSoftware\x00Adobe ImageReadyq?e<\x00\x00\x025IDAT8?\xa5\x93?K\x94Q\x14\x87\x9f\xf7?Q\x1bs4?\x03\x9a\xa8?B\x02\x8b$\x10[U;i\x13?6h?&h[?"\x14j?\xa2M\x7fB\x14F\x9aQ?&\x842?\x0b\x89"\x82??!?\x9c!\x9c2l??{N\x8bW\x9dY\xb4\t/\x1c?=\x9b?}????\xa9*;9!?\x83\x91?[?\\v*?D\x04\'`EpNp\xa2X\'U?pVq"Sw.\x1e?\x08\x01D?jw????\xbc??7{|\x9b?\x89$\x01??W@\x15\x9c\x05q`Lt/\x97?\x94\xa1d?\x18~?\x18?\x18W[%\xb0?\x83??\x14\x88\x8dB?\xa6H\tL\tl\x19>/\x01`\xac\xabx?\x9cl\nx\xb0\x98\x07\x95\x88D$"q[\x19?d\x00(o\n\xa0??\x7f\xb9\xa4?\x1bF\x1f\x8e\xac\xa8?j??eUU}?.?\x9f\x8cE??x\x94??\r\xbdtoJU5"0N\x10U?\x00??V\t\x02\x9f\x81?U?\x00\x9eM\xae2?r\x9b7\x83\x82\x8aP3????.?&"?\xb7ZP \x0c<?O\xa5\t}\xb8?\x99\xa6?\x87?\x1di|/\xa0??0\xbe\x1fp?d&\x1a\xad\x95\x8a\x07?\t*\x10??b:?d?.\x13C\x8a?\x12\xbe\xbf\x8e?{???\x08?\x80\xa7\x13+d\x13>J?\x80\x15T\x95\x9a\x00??S\x8c\r?\xa1\x03\x07?\x96\x9b\xa7\xab=E??\xa4\xb3?\x19q??B\x91=\x8d??k?J\x0bV"??\xf7x?\xa1\x00?\\.\x87\x87???\x02F@D\x99],??\x10#?X\xb7=\xb9\x10?Z\x1by???cI??\x1ag?\x92\xbc?T?t[\x92\x81?<_\x17~\x92\x88?H%?\x10Q\x02\x9f\n\x81qQ\x0bm?\x1bX?\xb1AK\xa6\x9e\xb9?u\xb2?1\xbe|/\x92M@\xa2!F?\xa9>"\r<DT?>\x92\x8e?>\x9a9Qv\x127?a\xac?Y?8?:??]X???9\x80\xb7?u?\x0b#BZ\x8d=\x1d?p\x00\x00\x00\x00IEND\xaeB`\x82"""
178 177 filenode = FileNode('calendar.png', content=data)
179 178 self.assertTrue(filenode.is_binary)
180 179
181 180
182 181 if __name__ == '__main__':
183 182 unittest.main()
General Comments 0
You need to be logged in to leave comments. Login now