6517cbcd8d76a5fba7be200da423798c959fdaba
[tools/itest-core.git] / imgdiff / trivial.py
1 """This module provides classes to deal with
2 unimportant difference in diff result.
3 """
4 import os
5 import re
6 import json
7 import fnmatch
8
9
10 class Conf(dict):
11     """
12     Configuration defining unimportant difference
13     """
14
15     @classmethod
16     def load(cls, filename):
17         "Load config from file"
18         with open(filename) as reader:
19             txt = reader.read()
20         txt.replace(os.linesep, '')
21         data = json.loads(txt)
22         return cls(data)
23
24
25 class Rules(object):
26     """
27     Unimportant rules
28     """
29     def __init__(self, conf):
30         self._rules = self._compile(conf)
31
32     def check_and_mark(self, item):
33         """Check if there are unimportant differences in item.
34         Mark them as ignore
35         """
36         for matcher, rule in self._rules:
37             if matcher(item['filename']):
38                 rule(item)
39                 break
40
41     @staticmethod
42     def _compile(conf):
43         """Compile config item to matching rules
44         """
45         def new_matcher(pattern):
46             """Supported file name pattern like:
47             *.log
48             partition_tab.txt
49             /tmp/a.txt
50             /dev/
51             some/file.txt
52             """
53             if pattern.endswith(os.path.sep):  # direcotry name
54                 pattern = pattern + '*'
55
56             bname = os.path.basename(pattern)
57             if bname == pattern:  # only basename, ignore dirname
58                 def matcher(filename):
59                     "Matcher"
60                     return fnmatch.fnmatch(os.path.basename(filename), pattern)
61             else:
62                 def matcher(filename):
63                     "Matcher"
64                     return fnmatch.fnmatch(filename, pattern)
65
66             matcher.__docstring__ = 'Match filename with pattern %s' % pattern
67             return matcher
68
69         rules = []
70         for pat in conf.get('ignoreFiles', []):
71             matcher = new_matcher(pat)
72             rules.append((matcher, ignore_file))
73
74         for entry in conf.get('ignoreLines', []):
75             files = entry['Files']
76             lines = entry['Lines']
77             if isinstance(files, basestring):
78                 files = [files]
79             if isinstance(lines, basestring):
80                 lines = [lines]
81             ignore = IgnoreLines(lines)
82             for pat in files:
83                 matcher = new_matcher(pat)
84                 rules.append((matcher, ignore))
85
86         return rules
87
88
89 def ignore_file(onefile):
90     """Mark whole file as trivial difference
91     """
92     onefile['ignore'] = True
93
94
95 class IgnoreLines(object):
96     """Mark certain lines in a file as trivial
97     differences according to given patterns
98     """
99     def __init__(self, patterns):
100         self.patterns = [re.compile(p) for p in patterns]
101
102     def is_unimportant(self, line):
103         "Is this line trivial"
104         for pat in self.patterns:
105             if pat.match(line['text']):
106                 return True
107
108     def __call__(self, onefile):
109         "Mark lines as trivial"
110         if onefile['type'] != 'onefilediff':
111             return
112
113         def should_ignore(line):
114             "Is this line trivial"
115             if line['type'] in ('insert', 'delete'):
116                 return self.is_unimportant(line)
117             # else: context, no_newline_at_eof
118             return True
119
120         all_ignored = True
121         for section in onefile['sections']:
122             for line in section['hunks']:
123                 line['ignore'] = should_ignore(line)
124                 all_ignored = all_ignored and line['ignore']
125
126         # if all lines are unimportant then the whole file is unimportant
127         if all_ignored:
128             onefile['ignore'] = True