ยปCore Development>Code coverage>Lib/test/test_mutants.py

Python code coverage for Lib/test/test_mutants.py

#countcontent
1n/afrom test.support import verbose, TESTFN
2n/aimport random
3n/aimport os
4n/a
5n/a# From SF bug #422121: Insecurities in dict comparison.
6n/a
7n/a# Safety of code doing comparisons has been an historical Python weak spot.
8n/a# The problem is that comparison of structures written in C *naturally*
9n/a# wants to hold on to things like the size of the container, or "the
10n/a# biggest" containee so far, across a traversal of the container; but
11n/a# code to do containee comparisons can call back into Python and mutate
12n/a# the container in arbitrary ways while the C loop is in midstream. If the
13n/a# C code isn't extremely paranoid about digging things out of memory on
14n/a# each trip, and artificially boosting refcounts for the duration, anything
15n/a# from infinite loops to OS crashes can result (yes, I use Windows <wink>).
16n/a#
17n/a# The other problem is that code designed to provoke a weakness is usually
18n/a# white-box code, and so catches only the particular vulnerabilities the
19n/a# author knew to protect against. For example, Python's list.sort() code
20n/a# went thru many iterations as one "new" vulnerability after another was
21n/a# discovered.
22n/a#
23n/a# So the dict comparison test here uses a black-box approach instead,
24n/a# generating dicts of various sizes at random, and performing random
25n/a# mutations on them at random times. This proved very effective,
26n/a# triggering at least six distinct failure modes the first 20 times I
27n/a# ran it. Indeed, at the start, the driver never got beyond 6 iterations
28n/a# before the test died.
29n/a
30n/a# The dicts are global to make it easy to mutate tham from within functions.
31n/adict1 = {}
32n/adict2 = {}
33n/a
34n/a# The current set of keys in dict1 and dict2. These are materialized as
35n/a# lists to make it easy to pick a dict key at random.
36n/adict1keys = []
37n/adict2keys = []
38n/a
39n/a# Global flag telling maybe_mutate() whether to *consider* mutating.
40n/amutate = 0
41n/a
42n/a# If global mutate is true, consider mutating a dict. May or may not
43n/a# mutate a dict even if mutate is true. If it does decide to mutate a
44n/a# dict, it picks one of {dict1, dict2} at random, and deletes a random
45n/a# entry from it; or, more rarely, adds a random element.
46n/a
47n/adef maybe_mutate():
48n/a global mutate
49n/a if not mutate:
50n/a return
51n/a if random.random() < 0.5:
52n/a return
53n/a
54n/a if random.random() < 0.5:
55n/a target, keys = dict1, dict1keys
56n/a else:
57n/a target, keys = dict2, dict2keys
58n/a
59n/a if random.random() < 0.2:
60n/a # Insert a new key.
61n/a mutate = 0 # disable mutation until key inserted
62n/a while 1:
63n/a newkey = Horrid(random.randrange(100))
64n/a if newkey not in target:
65n/a break
66n/a target[newkey] = Horrid(random.randrange(100))
67n/a keys.append(newkey)
68n/a mutate = 1
69n/a
70n/a elif keys:
71n/a # Delete a key at random.
72n/a mutate = 0 # disable mutation until key deleted
73n/a i = random.randrange(len(keys))
74n/a key = keys[i]
75n/a del target[key]
76n/a del keys[i]
77n/a mutate = 1
78n/a
79n/a# A horrid class that triggers random mutations of dict1 and dict2 when
80n/a# instances are compared.
81n/a
82n/aclass Horrid:
83n/a def __init__(self, i):
84n/a # Comparison outcomes are determined by the value of i.
85n/a self.i = i
86n/a
87n/a # An artificial hashcode is selected at random so that we don't
88n/a # have any systematic relationship between comparison outcomes
89n/a # (based on self.i and other.i) and relative position within the
90n/a # hash vector (based on hashcode).
91n/a # XXX This is no longer effective.
92n/a ##self.hashcode = random.randrange(1000000000)
93n/a
94n/a def __hash__(self):
95n/a return 42
96n/a return self.hashcode
97n/a
98n/a def __eq__(self, other):
99n/a maybe_mutate() # The point of the test.
100n/a return self.i == other.i
101n/a
102n/a def __ne__(self, other):
103n/a raise RuntimeError("I didn't expect some kind of Spanish inquisition!")
104n/a
105n/a __lt__ = __le__ = __gt__ = __ge__ = __ne__
106n/a
107n/a def __repr__(self):
108n/a return "Horrid(%d)" % self.i
109n/a
110n/a# Fill dict d with numentries (Horrid(i), Horrid(j)) key-value pairs,
111n/a# where i and j are selected at random from the candidates list.
112n/a# Return d.keys() after filling.
113n/a
114n/adef fill_dict(d, candidates, numentries):
115n/a d.clear()
116n/a for i in range(numentries):
117n/a d[Horrid(random.choice(candidates))] = \
118n/a Horrid(random.choice(candidates))
119n/a return list(d.keys())
120n/a
121n/a# Test one pair of randomly generated dicts, each with n entries.
122n/a# Note that dict comparison is trivial if they don't have the same number
123n/a# of entires (then the "shorter" dict is instantly considered to be the
124n/a# smaller one, without even looking at the entries).
125n/a
126n/adef test_one(n):
127n/a global mutate, dict1, dict2, dict1keys, dict2keys
128n/a
129n/a # Fill the dicts without mutating them.
130n/a mutate = 0
131n/a dict1keys = fill_dict(dict1, range(n), n)
132n/a dict2keys = fill_dict(dict2, range(n), n)
133n/a
134n/a # Enable mutation, then compare the dicts so long as they have the
135n/a # same size.
136n/a mutate = 1
137n/a if verbose:
138n/a print("trying w/ lengths", len(dict1), len(dict2), end=' ')
139n/a while dict1 and len(dict1) == len(dict2):
140n/a if verbose:
141n/a print(".", end=' ')
142n/a c = dict1 == dict2
143n/a if verbose:
144n/a print()
145n/a
146n/a# Run test_one n times. At the start (before the bugs were fixed), 20
147n/a# consecutive runs of this test each blew up on or before the sixth time
148n/a# test_one was run. So n doesn't have to be large to get an interesting
149n/a# test.
150n/a# OTOH, calling with large n is also interesting, to ensure that the fixed
151n/a# code doesn't hold on to refcounts *too* long (in which case memory would
152n/a# leak).
153n/a
154n/adef test(n):
155n/a for i in range(n):
156n/a test_one(random.randrange(1, 100))
157n/a
158n/a# See last comment block for clues about good values for n.
159n/atest(100)
160n/a
161n/a##########################################################################
162n/a# Another segfault bug, distilled by Michael Hudson from a c.l.py post.
163n/a
164n/aclass Child:
165n/a def __init__(self, parent):
166n/a self.__dict__['parent'] = parent
167n/a def __getattr__(self, attr):
168n/a self.parent.a = 1
169n/a self.parent.b = 1
170n/a self.parent.c = 1
171n/a self.parent.d = 1
172n/a self.parent.e = 1
173n/a self.parent.f = 1
174n/a self.parent.g = 1
175n/a self.parent.h = 1
176n/a self.parent.i = 1
177n/a return getattr(self.parent, attr)
178n/a
179n/aclass Parent:
180n/a def __init__(self):
181n/a self.a = Child(self)
182n/a
183n/a# Hard to say what this will print! May vary from time to time. But
184n/a# we're specifically trying to test the tp_print slot here, and this is
185n/a# the clearest way to do it. We print the result to a temp file so that
186n/a# the expected-output file doesn't need to change.
187n/a
188n/af = open(TESTFN, "w")
189n/aprint(Parent().__dict__, file=f)
190n/af.close()
191n/aos.unlink(TESTFN)
192n/a
193n/a##########################################################################
194n/a# And another core-dumper from Michael Hudson.
195n/a
196n/adict = {}
197n/a
198n/a# Force dict to malloc its table.
199n/afor i in range(1, 10):
200n/a dict[i] = i
201n/a
202n/af = open(TESTFN, "w")
203n/a
204n/aclass Machiavelli:
205n/a def __repr__(self):
206n/a dict.clear()
207n/a
208n/a # Michael sez: "doesn't crash without this. don't know why."
209n/a # Tim sez: "luck of the draw; crashes with or without for me."
210n/a print(file=f)
211n/a
212n/a return repr("machiavelli")
213n/a
214n/a def __hash__(self):
215n/a return 0
216n/a
217n/adict[Machiavelli()] = Machiavelli()
218n/a
219n/aprint(str(dict), file=f)
220n/af.close()
221n/aos.unlink(TESTFN)
222n/adel f, dict
223n/a
224n/a
225n/a##########################################################################
226n/a# And another core-dumper from Michael Hudson.
227n/a
228n/adict = {}
229n/a
230n/a# let's force dict to malloc its table
231n/afor i in range(1, 10):
232n/a dict[i] = i
233n/a
234n/aclass Machiavelli2:
235n/a def __eq__(self, other):
236n/a dict.clear()
237n/a return 1
238n/a
239n/a def __hash__(self):
240n/a return 0
241n/a
242n/adict[Machiavelli2()] = Machiavelli2()
243n/a
244n/atry:
245n/a dict[Machiavelli2()]
246n/aexcept KeyError:
247n/a pass
248n/a
249n/adel dict
250n/a
251n/a##########################################################################
252n/a# And another core-dumper from Michael Hudson.
253n/a
254n/adict = {}
255n/a
256n/a# let's force dict to malloc its table
257n/afor i in range(1, 10):
258n/a dict[i] = i
259n/a
260n/aclass Machiavelli3:
261n/a def __init__(self, id):
262n/a self.id = id
263n/a
264n/a def __eq__(self, other):
265n/a if self.id == other.id:
266n/a dict.clear()
267n/a return 1
268n/a else:
269n/a return 0
270n/a
271n/a def __repr__(self):
272n/a return "%s(%s)"%(self.__class__.__name__, self.id)
273n/a
274n/a def __hash__(self):
275n/a return 0
276n/a
277n/adict[Machiavelli3(1)] = Machiavelli3(0)
278n/adict[Machiavelli3(2)] = Machiavelli3(0)
279n/a
280n/af = open(TESTFN, "w")
281n/atry:
282n/a try:
283n/a print(dict[Machiavelli3(2)], file=f)
284n/a except KeyError:
285n/a pass
286n/afinally:
287n/a f.close()
288n/a os.unlink(TESTFN)
289n/a
290n/adel dict
291n/adel dict1, dict2, dict1keys, dict2keys