1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
|
"""Module to access and modify tag configuration files used by resmoke."""
from __future__ import absolute_import
from __future__ import print_function
import collections
import copy
import textwrap
import yaml
# Setup to preserve order in yaml.dump, see https://stackoverflow.com/a/8661021
def _represent_dict_order(self, data):
return self.represent_mapping("tag:yaml.org,2002:map", data.items())
yaml.add_representer(collections.OrderedDict, _represent_dict_order)
# End setup
class TagsConfig(object):
"""Represent a test tag configuration file."""
def __init__(self, raw, cmp_func=None):
"""Initialize a TagsConfig from a dict representing the associations between tests and tags.
'cmp_func' can be used to specify a comparison function that will be used when sorting tags.
"""
self.raw = raw
self._conf = self.raw["selector"]
self._conf_copy = copy.deepcopy(self._conf)
self._cmp_func = cmp_func
@classmethod
def from_file(cls, filename, **kwargs):
"""Return a TagsConfig from a file containing the associations between tests and tags.
See TagsConfig.__init__() for the keyword arguments that can be specified.
"""
with open(filename, "r") as fstream:
raw = yaml.safe_load(fstream)
return cls(raw, **kwargs)
@classmethod
def from_dict(cls, raw, **kwargs):
"""Return a TagsConfig from a dict representing the associations between tests and tags.
See TagsConfig.__init__() for the keyword arguments that can be specified.
"""
return cls(copy.deepcopy(raw), **kwargs)
def get_test_kinds(self):
"""List the test kinds."""
return self._conf.keys()
def get_test_patterns(self, test_kind):
"""List the test patterns under 'test_kind'."""
return getdefault(self._conf, test_kind, {}).keys()
def get_tags(self, test_kind, test_pattern):
"""List the tags under 'test_kind' and 'test_pattern'."""
patterns = getdefault(self._conf, test_kind, {})
return getdefault(patterns, test_pattern, [])
def add_tag(self, test_kind, test_pattern, tag):
"""Add a tag. Return True if the tag is added or False if the tag was already present."""
patterns = setdefault(self._conf, test_kind, {})
tags = setdefault(patterns, test_pattern, [])
if tag not in tags:
tags.append(tag)
tags.sort(cmp=self._cmp_func)
return True
return False
def remove_tag(self, test_kind, test_pattern, tag):
"""Remove a tag. Return True if the tag was removed or False if the tag was not present."""
patterns = self._conf.get(test_kind)
if not patterns or test_pattern not in patterns:
return False
tags = patterns.get(test_pattern)
if tags and tag in tags:
tags[:] = (value for value in tags if value != tag)
# Remove the pattern if there are no associated tags.
if not tags:
del patterns[test_pattern]
return True
return False
def remove_test_pattern(self, test_kind, test_pattern):
"""Remove a test pattern."""
patterns = self._conf.get(test_kind)
if not patterns or test_pattern not in patterns:
return
del patterns[test_pattern]
def is_modified(self):
"""Return True if the tags have been modified, False otherwise."""
return self._conf != self._conf_copy
def write_file(self, filename, preamble=None):
"""Write the tags to a file.
If 'preamble' is present it will be added as a comment at the top of the file.
"""
with open(filename, "w") as fstream:
if preamble:
print(textwrap.fill(preamble, width=100, initial_indent="# ",
subsequent_indent="# "), file=fstream)
# We use yaml.safe_dump() in order avoid having strings being written to the file as
# "!!python/unicode ..." and instead have them written as plain 'str' instances.
yaml.safe_dump(self.raw, fstream, default_flow_style=False)
def getdefault(doc, key, default):
"""Return the value in 'doc' with key 'key' if it is present and not None, returns
the specified default value otherwise."""
value = doc.get(key)
if value is not None:
return value
else:
return default
def setdefault(doc, key, default):
"""Return the value in 'doc' with key 'key' if it is present and not None, sets the value
to default and return it otherwise."""
value = doc.setdefault(key, default)
if value is not None:
return value
else:
doc[key] = default
return default
|