• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

smythi93 / Tests4Py / 30040549735

23 Jul 2026 08:04PM UTC coverage: 23.663% (-21.5%) from 45.174%
30040549735

push

github

web-flow
Merge pull request #106 from smythi93/dev

Release 1.0.0 — complete benchmark coverage (328/328 reproducible bugs)

11882 of 64778 new or added lines in 1205 files covered. (18.34%)

46 existing lines in 14 files now uncovered.

18085 of 76426 relevant lines covered (23.66%)

0.24 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

36.1
/src/tests4py/projects/matplotlib.py
1
import ast
1✔
2
import bisect
1✔
3
import os
1✔
4
import random
1✔
5
import string
1✔
6
import subprocess
1✔
7
from pathlib import Path
1✔
8
from typing import Any, List, Optional, Tuple
1✔
9

10
from tests4py.grammars import python
1✔
11
from tests4py.grammars.default import clean_up, INTEGER, FLOAT, NUMBER
1✔
12
from tests4py.grammars.fuzzer import Grammar, is_valid_grammar, srange
1✔
13
from tests4py.projects import Project, Status, TestingFramework, TestStatus
1✔
14
from tests4py.tests.generator import UnittestGenerator, SystemtestGenerator
1✔
15
from tests4py.tests.utils import API, TestResult
1✔
16

17
PROJECT_NAME = "matplotlib"
1✔
18

19
# On an Apple-Silicon host the pyenv CPython 3.8.4 runs as x86_64 (Rosetta), but
20
# clang defaults to the native arm64 target which the ancient numpy 1.15 headers
21
# bundled with these matplotlib versions cannot classify ("Unknown CPU").  Force
22
# the C/C++ extensions to build for x86_64 so they match the interpreter.  The
23
# ft2font patch works around a hard C++ type error (char* vs unsigned char*) that
24
# modern system FreeType headers trigger; the replace is a no-op when absent.
25
_FT2FONT_PATCH = (
1✔
26
    "import io\n"
27
    "p = 'src/ft2font.cpp'\n"
28
    "try:\n"
29
    "    s = io.open(p, encoding='utf8').read()\n"
30
    "except OSError:\n"
31
    "    raise SystemExit(0)\n"
32
    "s = s.replace('tags = outline.tags + first;', "
33
    "'tags = (char *)(outline.tags + first);')\n"
34
    "io.open(p, 'w', encoding='utf8').write(s)\n"
35
)
36

37
# Older matplotlib commits download and build a local FreeType 2.6.1 whose
38
# bundled zlib no longer compiles against modern macOS headers ("unknown type
39
# name 'Byte'" in ftgzip.c).  Newer commits already link the system FreeType.
40
# Force *system* FreeType for every build so the extensions link the brew
41
# freetype2 (found via pkg-config) instead of the broken local build.  configparser
42
# is used so an existing setup.cfg is preserved rather than clobbered.
43
_SYSTEM_FREETYPE_PATCH = (
1✔
44
    "import configparser, os\n"
45
    "c = configparser.ConfigParser()\n"
46
    "if os.path.exists('setup.cfg'):\n"
47
    "    c.read('setup.cfg')\n"
48
    "if not c.has_section('libs'):\n"
49
    "    c.add_section('libs')\n"
50
    "c.set('libs', 'system_freetype', 'True')\n"
51
    "with open('setup.cfg', 'w') as f:\n"
52
    "    c.write(f)\n"
53
)
54

55

56
class Matplotlib(Project):
1✔
57
    def __init__(
1✔
58
        self,
59
        bug_id: int,
60
        buggy_commit_id: str,
61
        fixed_commit_id: str,
62
        test_files: List[Path],
63
        test_cases: List[str],
64
        test_status_fixed: TestStatus = TestStatus.PASSING,
65
        test_status_buggy: TestStatus = TestStatus.FAILING,
66
        unittests: Optional[UnittestGenerator] = None,
67
        systemtests: Optional[SystemtestGenerator] = None,
68
        api: Optional[API] = None,
69
        grammar: Optional[Grammar] = None,
70
        loc: int = 0,
71
        relevant_test_files: Optional[List[Path]] = None,
72
        skip_tests: Optional[List[str]] = None,
73
        python_version: Optional[str] = None,
74
    ):
75
        super().__init__(
1✔
76
            bug_id=bug_id,
77
            project_name=PROJECT_NAME,
78
            github_url="https://github.com/matplotlib/matplotlib",
79
            status=Status.OK,
80
            python_version=python_version or "3.8.4",
81
            python_path="",
82
            buggy_commit_id=buggy_commit_id,
83
            fixed_commit_id=fixed_commit_id,
84
            testing_framework=TestingFramework.PYTEST,
85
            test_files=test_files,
86
            test_cases=test_cases,
87
            test_status_fixed=test_status_fixed,
88
            test_status_buggy=test_status_buggy,
89
            unittests=unittests,
90
            systemtests=systemtests,
91
            api=api,
92
            grammar=grammar,
93
            loc=loc,
94
            test_base=Path("lib", PROJECT_NAME, "tests"),
95
            source_base=Path("lib", PROJECT_NAME),
96
            setup=[
97
                ["python", "-c", _SYSTEM_FREETYPE_PATCH],
98
                ["python", "-c", _FT2FONT_PATCH],
99
                ["python", "-m", "pip", "install", "-e", "."],
100
            ],
101
            setup_env={"ARCHFLAGS": "-arch x86_64"},
102
            included_files=[os.path.join("lib", PROJECT_NAME)],
103
            excluded_files=[os.path.join("lib", PROJECT_NAME, "tests")],
104
            relevant_test_files=relevant_test_files,
105
            skip_tests=skip_tests,
106
        )
107

108

109
def register():
1✔
110
    Matplotlib(
1✔
111
        bug_id=1,
112
        buggy_commit_id="c404d1f716e8aaefd4d7371ff49673e9c1f7f07c",
113
        fixed_commit_id="5324adaec6a7fd3d78dea7b28451d5f6e95392a6",
114
        test_files=[
115
            Path("lib", "matplotlib", "tests", "test_bbox_tight.py"),
116
        ],
117
        test_cases=[
118
            os.path.join(
119
                "lib", "matplotlib", "tests", "test_bbox_tight.py::test_noop_tight_bbox"
120
            )
121
        ],
122
        unittests=Matplotlib1UnittestGenerator(),
123
        systemtests=Matplotlib1SystemtestGenerator(),
124
        api=Matplotlib1API(),
125
        grammar=grammar_1,
126
        loc=63544,
127
    )
128
    Matplotlib(
1✔
129
        bug_id=2,
130
        buggy_commit_id="2a3707d9c3472b1a010492322b6946388d4989ae",
131
        fixed_commit_id="d86cc2bab8183fd3288ed474e4dfd33e0f018908",
132
        test_files=[Path("lib", "matplotlib", "tests", "test_axes.py")],
133
        test_cases=[
134
            os.path.join(
135
                "lib",
136
                "matplotlib",
137
                "tests",
138
                "test_axes.py::TestScatter::test_scatter_unfilled",
139
            )
140
        ],
141
        relevant_test_files=[
142
            os.path.join(
143
                "lib",
144
                "matplotlib",
145
                "tests",
146
                "test_axes.py::TestScatter",
147
            )
148
        ],
149
        unittests=Matplotlib2UnittestGenerator(),
150
        systemtests=Matplotlib2SystemtestGenerator(),
151
        api=Matplotlib2API(),
152
        grammar=grammar_2,
153
        loc=63506,
154
    )
155
    Matplotlib(
1✔
156
        bug_id=3,
157
        buggy_commit_id="5e046f72ae82788788c7e9b9354b87b131891cd8",
158
        fixed_commit_id="2a3707d9c3472b1a010492322b6946388d4989ae",
159
        test_files=[Path("lib", "matplotlib", "tests", "test_marker.py")],
160
        test_cases=[
161
            os.path.join(
162
                "lib", "matplotlib", "tests", "test_marker.py::test_marker_fillstyle"
163
            )
164
        ],
165
        unittests=Matplotlib3UnittestGenerator(),
166
        systemtests=Matplotlib3SystemtestGenerator(),
167
        api=Matplotlib3API(),
168
        grammar=grammar_3,
169
        loc=63506,
170
    )
171
    Matplotlib(
1✔
172
        bug_id=4,
173
        buggy_commit_id="793c6b05381231371267b44b107726f3878e14f2",
174
        fixed_commit_id="fafa132484872141431a5be3727eab1d8b3c7b82",
175
        test_files=[Path("lib", "matplotlib", "tests", "test_axes.py")],
176
        test_cases=[
177
            os.path.join(
178
                "lib", "matplotlib", "tests", "test_axes.py::test_vlines_default"
179
            ),
180
            os.path.join(
181
                "lib", "matplotlib", "tests", "test_axes.py::test_hlines_default"
182
            ),
183
        ],
184
        relevant_test_files=[
185
            os.path.join("lib", "matplotlib", "tests", "test_axes.py::test_vlines"),
186
            os.path.join(
187
                "lib", "matplotlib", "tests", "test_axes.py::test_vlines_default"
188
            ),
189
            os.path.join(
190
                "lib", "matplotlib", "tests", "test_axes.py::test_vline_limit"
191
            ),
192
            os.path.join("lib", "matplotlib", "tests", "test_axes.py::test_hlines"),
193
            os.path.join(
194
                "lib", "matplotlib", "tests", "test_axes.py::test_hlines_default"
195
            ),
196
        ],
197
        unittests=Matplotlib4UnittestGenerator(),
198
        systemtests=Matplotlib4SystemtestGenerator(),
199
        api=Matplotlib4API(),
200
        grammar=grammar_4,
201
        loc=63462,
202
    )
203
    Matplotlib(
1✔
204
        bug_id=5,
205
        buggy_commit_id="49593b73854e10ace8f3c05343220328def6a328",
206
        fixed_commit_id="66289c4f1895b8c65ca92a03d92f6b1cfa552267",
207
        test_files=[Path("lib", "matplotlib", "tests", "test_axes.py")],
208
        test_cases=[
209
            os.path.join(
210
                "lib",
211
                "matplotlib",
212
                "tests",
213
                "test_axes.py::TestScatter::test_scatter_linewidths",
214
            )
215
        ],
216
        relevant_test_files=[
217
            os.path.join(
218
                "lib",
219
                "matplotlib",
220
                "tests",
221
                "test_axes.py::TestScatter",
222
            )
223
        ],
224
        unittests=Matplotlib5UnittestGenerator(),
225
        systemtests=Matplotlib5SystemtestGenerator(),
226
        api=Matplotlib5API(),
227
        grammar=grammar_5,
228
        loc=63378,
229
    )
230
    Matplotlib(
1✔
231
        bug_id=6,
232
        buggy_commit_id="fb9e72309aeefb51d1a56ecbdb1e7399d105ff06",
233
        fixed_commit_id="d84f4a7ac8d43d1288cb8ce11ab60ae557306b7e",
234
        test_files=[Path("lib", "matplotlib", "tests", "test_axes.py")],
235
        test_cases=[
236
            os.path.join(
237
                "lib",
238
                "matplotlib",
239
                "tests",
240
                "test_axes.py::TestScatter::test_scatter_single_color_c[png]",
241
            )
242
        ],
243
        relevant_test_files=[
244
            os.path.join(
245
                "lib",
246
                "matplotlib",
247
                "tests",
248
                "test_axes.py::TestScatter",
249
            )
250
        ],
251
        unittests=Matplotlib6UnittestGenerator(),
252
        systemtests=Matplotlib6SystemtestGenerator(),
253
        api=Matplotlib6API(),
254
        grammar=grammar_6,
255
        loc=63369,
256
    )
257
    Matplotlib(
1✔
258
        bug_id=7,
259
        buggy_commit_id="969513e2a5227331a2eb9e4bc4ba8448a0f9831d",
260
        fixed_commit_id="ac400b51bb31b91920ee9aae02a0606a67983a8f",
261
        test_files=[Path("lib", "matplotlib", "tests", "test_colors.py")],
262
        test_cases=[
263
            os.path.join(
264
                "lib",
265
                "matplotlib",
266
                "tests",
267
                "test_colors.py::test_light_source_shading_empty_mask",
268
            )
269
        ],
270
        test_status_buggy=TestStatus.PASSING,
271
        loc=63292,
272
    )
273
    Matplotlib(
1✔
274
        bug_id=8,
275
        buggy_commit_id="54bd6f19b23dc940a4d572583c449613b6b1ae3c",
276
        fixed_commit_id="2f41868de465b86d2fc357f7ed58ff323d58030f",
277
        test_files=[Path("lib", "matplotlib", "tests", "test_axes.py")],
278
        test_cases=[
279
            os.path.join(
280
                "lib", "matplotlib", "tests", "test_axes.py::test_unautoscaley[True]"
281
            ),
282
            os.path.join(
283
                "lib", "matplotlib", "tests", "test_axes.py::test_unautoscalex[True]"
284
            ),
285
            os.path.join(
286
                "lib", "matplotlib", "tests", "test_axes.py::test_unautoscaley[None]"
287
            ),
288
            os.path.join(
289
                "lib", "matplotlib", "tests", "test_axes.py::test_unautoscalex[None]"
290
            ),
291
        ],
292
        unittests=Matplotlib8UnittestGenerator(),
293
        systemtests=Matplotlib8SystemtestGenerator(),
294
        api=Matplotlib8API(),
295
        grammar=grammar_8,
296
        relevant_test_files=[
297
            os.path.join(
298
                "lib", "matplotlib", "tests", "test_axes.py::test_unautoscaley"
299
            ),
300
            os.path.join(
301
                "lib", "matplotlib", "tests", "test_axes.py::test_unautoscalex"
302
            ),
303
            os.path.join(
304
                "lib", "matplotlib", "tests", "test_axes.py::test_shared_axes_autoscale"
305
            ),
306
            os.path.join(
307
                "lib", "matplotlib", "tests", "test_axes.py::test_twinx_axis_scales"
308
            ),
309
            os.path.join(
310
                "lib",
311
                "matplotlib",
312
                "tests",
313
                "test_axes.py::test_twin_inherit_autoscale_setting",
314
            ),
315
            os.path.join(
316
                "lib", "matplotlib", "tests", "test_axes.py::test_autoscale_tiny_range"
317
            ),
318
            os.path.join(
319
                "lib", "matplotlib", "tests", "test_axes.py::test_autoscale_tight"
320
            ),
321
            os.path.join(
322
                "lib", "matplotlib", "tests", "test_axes.py::test_autoscale_log_shared"
323
            ),
324
        ],
325
        loc=63243,
326
    )
327
    Matplotlib(
1✔
328
        bug_id=9,
329
        buggy_commit_id="8673167d4c44dc2ede02fedd718dadf7f88e102d",
330
        fixed_commit_id="c01f9d3eff9b6239446c1bb2d205eccd69054aeb",
331
        test_files=[Path("lib", "matplotlib", "tests", "test_polar.py")],
332
        test_cases=[
333
            os.path.join(
334
                "lib",
335
                "matplotlib",
336
                "tests",
337
                "test_polar.py::test_polar_invertedylim_rorigin[png]",
338
            )
339
        ],
340
        unittests=Matplotlib9UnittestGenerator(),
341
        systemtests=Matplotlib9SystemtestGenerator(),
342
        api=Matplotlib9API(),
343
        grammar=grammar_9,
344
        loc=63277,
345
    )
346
    Matplotlib(
1✔
347
        bug_id=10,
348
        buggy_commit_id="b31d64ce3910e8d297d8300690e459587f77181f",
349
        fixed_commit_id="1986da3968ee76c6bce8f4c04aed80e23bd4ecfa",
350
        test_files=[Path("lib", "matplotlib", "tests", "test_axes.py")],
351
        test_cases=[
352
            os.path.join(
353
                "lib", "matplotlib", "tests", "test_axes.py::test_offset_text_visible"
354
            )
355
        ],
356
        relevant_test_files=[
357
            os.path.join(
358
                "lib", "matplotlib", "tests", "test_axes.py::test_offset_text_visible"
359
            ),
360
            os.path.join(
361
                "lib", "matplotlib", "tests", "test_axes.py::test_offset_label_color"
362
            ),
363
            os.path.join(
364
                "lib", "matplotlib", "tests", "test_axes.py::test_text_labelsize"
365
            ),
366
            os.path.join(
367
                "lib", "matplotlib", "tests", "test_axes.py::test_large_offset"
368
            ),
369
            os.path.join(
370
                "lib", "matplotlib", "tests", "test_axes.py::test_errorbar_offsets"
371
            ),
372
            os.path.join(
373
                "lib", "matplotlib", "tests", "test_axes.py::test_hist_offset"
374
            ),
375
            os.path.join(
376
                "lib", "matplotlib", "tests", "test_axes.py::test_move_offsetlabel"
377
            ),
378
            os.path.join(
379
                "lib", "matplotlib", "tests", "test_axes.py::test_dash_offset"
380
            ),
381
            os.path.join(
382
                "lib", "matplotlib", "tests", "test_axes.py::test_relim_visible_only"
383
            ),
384
            os.path.join(
385
                "lib",
386
                "matplotlib",
387
                "tests",
388
                "test_axes.py::test_retain_tick_visibility",
389
            ),
390
        ],
391
        unittests=Matplotlib10UnittestGenerator(),
392
        systemtests=Matplotlib10SystemtestGenerator(),
393
        api=Matplotlib10API(),
394
        grammar=grammar_10,
395
        loc=62823,
396
    )
397
    Matplotlib(
1✔
398
        bug_id=11,
399
        buggy_commit_id="f8459a513c3f67447ceb1a07c29760d504517ff2",
400
        fixed_commit_id="af745264376a10782bd0d8b96d255f958c2950f3",
401
        test_files=[Path("lib", "matplotlib", "tests", "test_text.py")],
402
        test_cases=[
403
            os.path.join(
404
                "lib",
405
                "matplotlib",
406
                "tests",
407
                "test_text.py::test_non_default_dpi[empty]",
408
            )
409
        ],
410
        skip_tests=["test_text_repr"],
411
        unittests=Matplotlib11UnittestGenerator(),
412
        systemtests=Matplotlib11SystemtestGenerator(),
413
        api=Matplotlib11API(),
414
        grammar=grammar_11,
415
        loc=62827,
416
    )
417
    Matplotlib(
1✔
418
        bug_id=12,
419
        buggy_commit_id="e92685a26442bfced06067934f34c104487583a8",
420
        fixed_commit_id="382be60aec3e6ebbf92f3d5792ba059bf3cfe6cf",
421
        test_files=[Path("lib", "matplotlib", "tests", "test_axes.py")],
422
        test_cases=[
423
            os.path.join(
424
                "lib",
425
                "matplotlib",
426
                "tests",
427
                "test_axes.py::test_lines_with_colors[png-data0]",
428
            ),
429
            os.path.join(
430
                "lib",
431
                "matplotlib",
432
                "tests",
433
                "test_axes.py::test_lines_with_colors[png-data1]",
434
            ),
435
        ],
436
        relevant_test_files=[
437
            os.path.join(
438
                "lib", "matplotlib", "tests", "test_axes.py::test_lines_with_colors"
439
            ),
440
            os.path.join(
441
                "lib", "matplotlib", "tests", "test_axes.py::test_step_linestyle"
442
            ),
443
            os.path.join(
444
                "lib", "matplotlib", "tests", "test_axes.py::test_markevery_line"
445
            ),
446
            os.path.join(
447
                "lib", "matplotlib", "tests", "test_axes.py::test_eb_line_zorder"
448
            ),
449
            os.path.join(
450
                "lib", "matplotlib", "tests", "test_axes.py::test_pie_linewidth_0"
451
            ),
452
            os.path.join(
453
                "lib", "matplotlib", "tests", "test_axes.py::test_pie_linewidth_2"
454
            ),
455
            os.path.join(
456
                "lib", "matplotlib", "tests", "test_axes.py::test_zero_linewidth"
457
            ),
458
            os.path.join("lib", "matplotlib", "tests", "test_axes.py::test_vlines"),
459
            os.path.join(
460
                "lib", "matplotlib", "tests", "test_axes.py::test_vline_limit"
461
            ),
462
            os.path.join("lib", "matplotlib", "tests", "test_axes.py::test_hlines"),
463
            os.path.join(
464
                "lib", "matplotlib", "tests", "test_axes.py::test_eventplot_colors"
465
            ),
466
        ],
467
        unittests=Matplotlib12UnittestGenerator(),
468
        systemtests=Matplotlib12SystemtestGenerator(),
469
        api=Matplotlib12API(),
470
        grammar=grammar_12,
471
        loc=62948,
472
    )
473
    Matplotlib(
1✔
474
        bug_id=13,
475
        buggy_commit_id="2c845db9d961ddea6e072c3e6dbb0bec823ac25e",
476
        fixed_commit_id="eb4b15b47a4d019f0e9edb2fb0587aebc2dbd8e8",
477
        test_files=[Path("lib", "matplotlib", "tests", "test_path.py")],
478
        test_cases=[
479
            os.path.join(
480
                "lib",
481
                "matplotlib",
482
                "tests",
483
                "test_path.py::test_make_compound_path_stops",
484
            )
485
        ],
486
        unittests=Matplotlib13UnittestGenerator(),
487
        systemtests=Matplotlib13SystemtestGenerator(),
488
        api=Matplotlib13API(),
489
        grammar=grammar_13,
490
        loc=62951,
491
    )
492
    Matplotlib(
1✔
493
        bug_id=14,
494
        buggy_commit_id="dbc35a9d625e162445f864b9f463c9961888e901",
495
        fixed_commit_id="c986a12b6d0d0d44f5a87f7cd4408f38040e2537",
496
        test_files=[Path("lib", "matplotlib", "tests", "test_text.py")],
497
        test_cases=[
498
            os.path.join(
499
                "lib",
500
                "matplotlib",
501
                "tests",
502
                "test_text.py::test_fontproperties_kwarg_precedence",
503
            )
504
        ],
505
        skip_tests=["test_text_repr"],
506
        unittests=Matplotlib14UnittestGenerator(),
507
        systemtests=Matplotlib14SystemtestGenerator(),
508
        api=Matplotlib14API(),
509
        grammar=grammar_14,
510
        loc=64078,
511
    )
512
    Matplotlib(
1✔
513
        bug_id=15,
514
        buggy_commit_id="6a8e39f4eba28e99d7b1dd454b001b987dd0bbca",
515
        fixed_commit_id="c7df5d2770030fe4588a0fc1ab4449a689554dfc",
516
        test_files=[Path("lib", "matplotlib", "tests", "test_colors.py")],
517
        test_cases=[
518
            os.path.join(
519
                "lib", "matplotlib", "tests", "test_colors.py::test_SymLogNorm"
520
            ),
521
            os.path.join(
522
                "lib",
523
                "matplotlib",
524
                "tests",
525
                "test_colors.py::test_SymLogNorm_colorbar",
526
            ),
527
            os.path.join(
528
                "lib",
529
                "matplotlib",
530
                "tests",
531
                "test_colors.py::test_SymLogNorm_single_zero",
532
            ),
533
        ],
534
        relevant_test_files=[
535
            os.path.join(
536
                "lib", "matplotlib", "tests", "test_colors.py::test_BoundaryNorm"
537
            ),
538
            os.path.join(
539
                "lib", "matplotlib", "tests", "test_colors.py::test_lognorm_invalid"
540
            ),
541
            os.path.join("lib", "matplotlib", "tests", "test_colors.py::test_LogNorm"),
542
            os.path.join(
543
                "lib", "matplotlib", "tests", "test_colors.py::test_PowerNorm"
544
            ),
545
            os.path.join(
546
                "lib",
547
                "matplotlib",
548
                "tests",
549
                "test_colors.py::test_PowerNorm_translation_invariance",
550
            ),
551
            os.path.join(
552
                "lib",
553
                "matplotlib",
554
                "tests",
555
                "test_colors.py::test_TwoSlopeNorm_autoscale",
556
            ),
557
            os.path.join(
558
                "lib",
559
                "matplotlib",
560
                "tests",
561
                "test_colors.py::test_TwoSlopeNorm_autoscale_None_vmin",
562
            ),
563
            os.path.join(
564
                "lib",
565
                "matplotlib",
566
                "tests",
567
                "test_colors.py::test_TwoSlopeNorm_autoscale_None_vmax",
568
            ),
569
            os.path.join(
570
                "lib", "matplotlib", "tests", "test_colors.py::test_TwoSlopeNorm_scale"
571
            ),
572
            os.path.join(
573
                "lib",
574
                "matplotlib",
575
                "tests",
576
                "test_colors.py::test_TwoSlopeNorm_scaleout_center",
577
            ),
578
            os.path.join(
579
                "lib",
580
                "matplotlib",
581
                "tests",
582
                "test_colors.py::test_TwoSlopeNorm_scaleout_center_max",
583
            ),
584
            os.path.join(
585
                "lib", "matplotlib", "tests", "test_colors.py::test_TwoSlopeNorm_Even"
586
            ),
587
            os.path.join(
588
                "lib", "matplotlib", "tests", "test_colors.py::test_TwoSlopeNorm_Odd"
589
            ),
590
            os.path.join(
591
                "lib",
592
                "matplotlib",
593
                "tests",
594
                "test_colors.py::test_TwoSlopeNorm_VminEqualsVcenter",
595
            ),
596
            os.path.join(
597
                "lib",
598
                "matplotlib",
599
                "tests",
600
                "test_colors.py::test_TwoSlopeNorm_VmaxEqualsVcenter",
601
            ),
602
            os.path.join(
603
                "lib",
604
                "matplotlib",
605
                "tests",
606
                "test_colors.py::test_TwoSlopeNorm_VminGTVcenter",
607
            ),
608
            os.path.join(
609
                "lib",
610
                "matplotlib",
611
                "tests",
612
                "test_colors.py::test_TwoSlopeNorm_TwoSlopeNorm_VminGTVmax",
613
            ),
614
            os.path.join(
615
                "lib",
616
                "matplotlib",
617
                "tests",
618
                "test_colors.py::test_TwoSlopeNorm_VcenterGTVmax",
619
            ),
620
            os.path.join(
621
                "lib", "matplotlib", "tests", "test_colors.py::test_SymLogNorm"
622
            ),
623
            os.path.join(
624
                "lib", "matplotlib", "tests", "test_colors.py::test_SymLogNorm_colorbar"
625
            ),
626
            os.path.join(
627
                "lib",
628
                "matplotlib",
629
                "tests",
630
                "test_colors.py::test_SymLogNorm_single_zero",
631
            ),
632
        ],
633
        unittests=Matplotlib15UnittestGenerator(),
634
        systemtests=Matplotlib15SystemtestGenerator(),
635
        api=Matplotlib15API(),
636
        grammar=grammar_15,
637
        loc=64152,
638
    )
639
    Matplotlib(
1✔
640
        bug_id=16,
641
        buggy_commit_id="89ff308306bafac22647c050a42141f040210216",
642
        fixed_commit_id="5d99e151be80bcb0b3b6d081fd3038330f573d94",
643
        test_files=[Path("lib", "matplotlib", "tests", "test_colorbar.py")],
644
        test_cases=[
645
            os.path.join(
646
                "lib",
647
                "matplotlib",
648
                "tests",
649
                "test_colorbar.py::test_colorbar_int[clim0]",
650
            ),
651
            os.path.join(
652
                "lib",
653
                "matplotlib",
654
                "tests",
655
                "test_colorbar.py::test_colorbar_int[clim1]",
656
            ),
657
        ],
658
        skip_tests=["test_colorbar_positioning[png]"],
659
        unittests=Matplotlib16UnittestGenerator(),
660
        systemtests=Matplotlib16SystemtestGenerator(),
661
        api=Matplotlib16API(),
662
        grammar=grammar_16,
663
        loc=65317,
664
    )
665
    Matplotlib(
1✔
666
        bug_id=17,
667
        buggy_commit_id="58c66982d98851c56b045137ced803fd62c6c5e8",
668
        fixed_commit_id="05a5db0fec2eced55076736f0b9520641b279ad6",
669
        test_files=[Path("lib", "matplotlib", "tests", "test_colorbar.py")],
670
        test_cases=[
671
            os.path.join(
672
                "lib",
673
                "matplotlib",
674
                "tests",
675
                "test_colorbar.py::test_colorbar_int[clim0]",
676
            ),
677
            os.path.join(
678
                "lib",
679
                "matplotlib",
680
                "tests",
681
                "test_colorbar.py::test_colorbar_int[clim1]",
682
            ),
683
        ],
684
        skip_tests=["test_colorbar_positioning[png]"],
685
        unittests=Matplotlib17UnittestGenerator(),
686
        systemtests=Matplotlib17SystemtestGenerator(),
687
        api=Matplotlib17API(),
688
        grammar=grammar_17,
689
        loc=64261,
690
    )
691
    Matplotlib(
1✔
692
        bug_id=18,
693
        buggy_commit_id="7d958c63f4fbcd8a28df666d738400b251f89af7",
694
        fixed_commit_id="47479b04b6718a65ebaac094db106d44b40da509",
695
        test_files=[Path("lib", "matplotlib", "tests", "test_axes.py")],
696
        test_cases=[
697
            os.path.join(
698
                "lib", "matplotlib", "tests", "test_axes.py::test_polar_no_data"
699
            )
700
        ],
701
        relevant_test_files=[
702
            os.path.join(
703
                "lib", "matplotlib", "tests", "test_axes.py::test_polar_twice"
704
            ),
705
            os.path.join("lib", "matplotlib", "tests", "test_axes.py::test_polar_wrap"),
706
            os.path.join(
707
                "lib", "matplotlib", "tests", "test_axes.py::test_polar_units_1"
708
            ),
709
            os.path.join(
710
                "lib", "matplotlib", "tests", "test_axes.py::test_polar_units_2"
711
            ),
712
            os.path.join("lib", "matplotlib", "tests", "test_axes.py::test_polar_rlim"),
713
            os.path.join(
714
                "lib", "matplotlib", "tests", "test_axes.py::test_polar_rlim_bottom"
715
            ),
716
            os.path.join(
717
                "lib", "matplotlib", "tests", "test_axes.py::test_polar_rlim_zero"
718
            ),
719
            os.path.join(
720
                "lib", "matplotlib", "tests", "test_axes.py::test_polar_no_data"
721
            ),
722
            os.path.join(
723
                "lib",
724
                "matplotlib",
725
                "tests",
726
                "test_axes.py::test_polar_not_datalim_adjustable",
727
            ),
728
            os.path.join(
729
                "lib", "matplotlib", "tests", "test_axes.py::test_polar_gridlines"
730
            ),
731
        ],
732
        unittests=Matplotlib18UnittestGenerator(),
733
        systemtests=Matplotlib18SystemtestGenerator(),
734
        api=Matplotlib18API(),
735
        grammar=grammar_18,
736
        loc=65299,
737
    )
738
    Matplotlib(
1✔
739
        bug_id=19,
740
        buggy_commit_id="47cfa35fc01af26498f493affcd8cb42b8fb2fc8",
741
        fixed_commit_id="670d5614e884be8013cf5dbb2160b4d33314d771",
742
        test_files=[Path("lib", "matplotlib", "tests", "test_axes.py")],
743
        test_cases=[
744
            os.path.join(
745
                "lib", "matplotlib", "tests", "test_axes.py::test_polar_no_data"
746
            )
747
        ],
748
        relevant_test_files=[
749
            os.path.join(
750
                "lib", "matplotlib", "tests", "test_axes.py::test_polar_twice"
751
            ),
752
            os.path.join("lib", "matplotlib", "tests", "test_axes.py::test_polar_wrap"),
753
            os.path.join(
754
                "lib", "matplotlib", "tests", "test_axes.py::test_polar_units_1"
755
            ),
756
            os.path.join(
757
                "lib", "matplotlib", "tests", "test_axes.py::test_polar_units_2"
758
            ),
759
            os.path.join("lib", "matplotlib", "tests", "test_axes.py::test_polar_rlim"),
760
            os.path.join(
761
                "lib", "matplotlib", "tests", "test_axes.py::test_polar_rlim_bottom"
762
            ),
763
            os.path.join(
764
                "lib", "matplotlib", "tests", "test_axes.py::test_polar_rlim_zero"
765
            ),
766
            os.path.join(
767
                "lib", "matplotlib", "tests", "test_axes.py::test_polar_no_data"
768
            ),
769
            os.path.join(
770
                "lib",
771
                "matplotlib",
772
                "tests",
773
                "test_axes.py::test_polar_not_datalim_adjustable",
774
            ),
775
            os.path.join(
776
                "lib", "matplotlib", "tests", "test_axes.py::test_polar_gridlines"
777
            ),
778
        ],
779
        unittests=Matplotlib19UnittestGenerator(),
780
        systemtests=Matplotlib19SystemtestGenerator(),
781
        api=Matplotlib19API(),
782
        grammar=grammar_19,
783
        loc=64410,
784
    )
785
    Matplotlib(
1✔
786
        bug_id=20,
787
        buggy_commit_id="805f451b7122d1ef595c7a01592bb2f500aed41f",
788
        fixed_commit_id="5375487ab28c877c8008c5a178e0b81a6e267957",
789
        test_files=[Path("lib", "matplotlib", "tests", "test_axes.py")],
790
        test_cases=[
791
            os.path.join(
792
                "lib", "matplotlib", "tests", "test_axes.py::test_invisible_axes"
793
            )
794
        ],
795
        relevant_test_files=[
796
            os.path.join(
797
                "lib", "matplotlib", "tests", "test_axes.py::test_invisible_axes"
798
            ),
799
            os.path.join(
800
                "lib",
801
                "matplotlib",
802
                "tests",
803
                "test_axes.py::test_annotate_across_transforms",
804
            ),
805
            os.path.join(
806
                "lib",
807
                "matplotlib",
808
                "tests",
809
                "test_axes.py::test_twin_spines",
810
            ),
811
            os.path.join(
812
                "lib",
813
                "matplotlib",
814
                "tests",
815
                "test_axes.py::test_relim_visible_only",
816
            ),
817
            os.path.join(
818
                "lib",
819
                "matplotlib",
820
                "tests",
821
                "test_axes.py::test_axisbelow",
822
            ),
823
        ],
824
        unittests=Matplotlib20UnittestGenerator(),
825
        systemtests=Matplotlib20SystemtestGenerator(),
826
        api=Matplotlib20API(),
827
        grammar=grammar_20,
828
        loc=65299,
829
    )
830
    Matplotlib(
1✔
831
        bug_id=21,
832
        buggy_commit_id="e240493a899ac05cb992cdb88f5487386586090e",
833
        fixed_commit_id="6fceb054369445d0b20d2864957e8bcfd8d2cb87",
834
        test_files=[Path("lib", "matplotlib", "tests", "test_axes.py")],
835
        test_cases=[
836
            os.path.join(
837
                "lib",
838
                "matplotlib",
839
                "tests",
840
                "test_axes.py::test_boxplot_marker_behavior",
841
            ),
842
        ],
843
        unittests=Matplotlib21UnittestGenerator(),
844
        systemtests=Matplotlib21SystemtestGenerator(),
845
        api=Matplotlib21API(),
846
        grammar=grammar_21,
847
        relevant_test_files=[
848
            os.path.join(
849
                "lib", "matplotlib", "tests", "test_axes.py::test_boxplot_dates_pandas"
850
            ),
851
            os.path.join("lib", "matplotlib", "tests", "test_axes.py::test_boxplot"),
852
            os.path.join(
853
                "lib", "matplotlib", "tests", "test_axes.py::test_boxplot_sym2"
854
            ),
855
            os.path.join(
856
                "lib", "matplotlib", "tests", "test_axes.py::test_boxplot_sym"
857
            ),
858
            os.path.join(
859
                "lib",
860
                "matplotlib",
861
                "tests",
862
                "test_axes.py::test_boxplot_autorange_whiskers",
863
            ),
864
            os.path.join(
865
                "lib", "matplotlib", "tests", "test_axes.py::test_boxplot_rc_parameters"
866
            ),
867
            os.path.join(
868
                "lib", "matplotlib", "tests", "test_axes.py::test_boxplot_with_CIarray"
869
            ),
870
            os.path.join(
871
                "lib",
872
                "matplotlib",
873
                "tests",
874
                "test_axes.py::test_boxplot_no_weird_whisker",
875
            ),
876
            os.path.join(
877
                "lib", "matplotlib", "tests", "test_axes.py::test_boxplot_bad_medians_1"
878
            ),
879
            os.path.join(
880
                "lib", "matplotlib", "tests", "test_axes.py::test_boxplot_bad_medians_2"
881
            ),
882
            os.path.join(
883
                "lib", "matplotlib", "tests", "test_axes.py::test_boxplot_bad_ci_1"
884
            ),
885
            os.path.join(
886
                "lib", "matplotlib", "tests", "test_axes.py::test_boxplot_zorder"
887
            ),
888
            os.path.join(
889
                "lib", "matplotlib", "tests", "test_axes.py::test_boxplot_bad_ci_2"
890
            ),
891
            os.path.join(
892
                "lib",
893
                "matplotlib",
894
                "tests",
895
                "test_axes.py::test_boxplot_marker_behavior",
896
            ),
897
            os.path.join(
898
                "lib",
899
                "matplotlib",
900
                "tests",
901
                "test_axes.py::test_boxplot_mod_artist_after_plotting",
902
            ),
903
            os.path.join(
904
                "lib", "matplotlib", "tests", "test_axes.py::test_boxplot_not_single"
905
            ),
906
        ],
907
        loc=65200,
908
    )
909
    Matplotlib(
1✔
910
        bug_id=22,
911
        buggy_commit_id="a87cd8a4c43688137155accdc57ed64fb95e2d40",
912
        fixed_commit_id="2349d826a170a10510d1ee8be02eaff51f7ee89f",
913
        test_files=[Path("lib", "matplotlib", "tests", "test_axes.py")],
914
        test_cases=[
915
            os.path.join(
916
                "lib",
917
                "matplotlib",
918
                "tests",
919
                "test_axes.py::test_hist_datetime_datasets_bins[datetime.datetime]",
920
            ),
921
            os.path.join(
922
                "lib",
923
                "matplotlib",
924
                "tests",
925
                "test_axes.py::test_hist_datetime_datasets_bins[np.datetime64]",
926
            ),
927
        ],
928
        relevant_test_files=[
929
            os.path.join("lib", "matplotlib", "tests", "test_axes.py::test_hist_log_2"),
930
            os.path.join(
931
                "lib", "matplotlib", "tests", "test_axes.py::test_hist_bar_empty"
932
            ),
933
            os.path.join(
934
                "lib", "matplotlib", "tests", "test_axes.py::test_hist_step_empty"
935
            ),
936
            os.path.join(
937
                "lib", "matplotlib", "tests", "test_axes.py::test_hist_step_filled"
938
            ),
939
            os.path.join(
940
                "lib",
941
                "matplotlib",
942
                "tests",
943
                "test_axes.py::test_hist_unequal_bins_density",
944
            ),
945
            os.path.join(
946
                "lib",
947
                "matplotlib",
948
                "tests",
949
                "test_axes.py::test_hist_datetime_datasets",
950
            ),
951
            os.path.join(
952
                "lib",
953
                "matplotlib",
954
                "tests",
955
                "test_axes.py::test_hist_datetime_datasets_bins",
956
            ),
957
            os.path.join(
958
                "lib", "matplotlib", "tests", "test_axes.py::test_hist_with_empty_input"
959
            ),
960
            os.path.join(
961
                "lib", "matplotlib", "tests", "test_axes.py::test_hist2d_density_normed"
962
            ),
963
            os.path.join("lib", "matplotlib", "tests", "test_axes.py::test_hist_step"),
964
            os.path.join(
965
                "lib", "matplotlib", "tests", "test_axes.py::test_hist_step_bottom"
966
            ),
967
            os.path.join(
968
                "lib", "matplotlib", "tests", "test_axes.py::test_hist_emptydata"
969
            ),
970
            os.path.join(
971
                "lib", "matplotlib", "tests", "test_axes.py::test_hist_labels"
972
            ),
973
            os.path.join(
974
                "lib", "matplotlib", "tests", "test_axes.py::test_hist_auto_bins"
975
            ),
976
            os.path.join(
977
                "lib", "matplotlib", "tests", "test_axes.py::test_hist_nan_data"
978
            ),
979
            os.path.join(
980
                "lib",
981
                "matplotlib",
982
                "tests",
983
                "test_axes.py::test_hist_range_and_density",
984
            ),
985
        ],
986
        unittests=Matplotlib22UnittestGenerator(),
987
        systemtests=Matplotlib22SystemtestGenerator(),
988
        api=Matplotlib22API(),
989
        grammar=grammar_22,
990
        loc=65308,
991
    )
992
    Matplotlib(
1✔
993
        bug_id=23,
994
        buggy_commit_id="bb6a4af984778dbac55c8391ae29fa2b5b201361",
995
        fixed_commit_id="418a1adf597b4d7759dcd64b864bd64cc1b507f4",
996
        test_files=[Path("lib", "matplotlib", "tests", "test_axes.py")],
997
        test_cases=[
998
            os.path.join(
999
                "lib",
1000
                "matplotlib",
1001
                "tests",
1002
                "test_axes.py::test_aspect_nonlinear_adjustable_datalim",
1003
            )
1004
        ],
1005
        relevant_test_files=[
1006
            os.path.join(
1007
                "lib",
1008
                "matplotlib",
1009
                "tests",
1010
                "test_axes.py::test_aspect_nonlinear_adjustable_datalim",
1011
            ),
1012
            os.path.join(
1013
                "lib",
1014
                "matplotlib",
1015
                "tests",
1016
                "test_axes.py::test_aspect_nonlinear_adjustable_box",
1017
            ),
1018
            os.path.join(
1019
                "lib",
1020
                "matplotlib",
1021
                "tests",
1022
                "test_axes.py::test_shared_with_aspect_2",
1023
            ),
1024
            os.path.join(
1025
                "lib",
1026
                "matplotlib",
1027
                "tests",
1028
                "test_axes.py::test_shared_with_aspect_3",
1029
            ),
1030
            os.path.join(
1031
                "lib",
1032
                "matplotlib",
1033
                "tests",
1034
                "test_axes.py::test_inset",
1035
            ),
1036
            os.path.join(
1037
                "lib",
1038
                "matplotlib",
1039
                "tests",
1040
                "test_axes.py::test_zoom_inset",
1041
            ),
1042
        ],
1043
        unittests=Matplotlib23UnittestGenerator(),
1044
        systemtests=Matplotlib23SystemtestGenerator(),
1045
        api=Matplotlib23API(),
1046
        grammar=grammar_23,
1047
        loc=65354,
1048
    )
1049
    Matplotlib(
1✔
1050
        bug_id=24,
1051
        buggy_commit_id="9a5473dbac05f3d6773b42c8e16d58a7dc3159b8",
1052
        fixed_commit_id="407a9fe71a4c0a8ba4914b8f54f21d32d6dd2d74",
1053
        test_files=[Path("lib", "matplotlib", "tests", "test_axes.py")],
1054
        test_cases=[
1055
            os.path.join(
1056
                "lib", "matplotlib", "tests", "test_axes.py::test_set_ticks_inverted"
1057
            )
1058
        ],
1059
        relevant_test_files=[
1060
            os.path.join(
1061
                "lib", "matplotlib", "tests", "test_axes.py::test_set_ticks_inverted"
1062
            ),
1063
            os.path.join(
1064
                "lib", "matplotlib", "tests", "test_axes.py::test_inverted_limits"
1065
            ),
1066
            os.path.join(
1067
                "lib", "matplotlib", "tests", "test_axes.py::test_tick_label_update"
1068
            ),
1069
        ],
1070
        unittests=Matplotlib24UnittestGenerator(),
1071
        systemtests=Matplotlib24SystemtestGenerator(),
1072
        api=Matplotlib24API(),
1073
        grammar=grammar_24,
1074
        loc=66180,
1075
    )
1076
    Matplotlib(
1✔
1077
        bug_id=25,
1078
        buggy_commit_id="9a5473dbac05f3d6773b42c8e16d58a7dc3159b8",
1079
        fixed_commit_id="184225bc5639fbd2f29c1253602806a8b6462d9f",
1080
        test_files=[Path("lib", "matplotlib", "tests", "test_collections.py")],
1081
        test_cases=[
1082
            os.path.join(
1083
                "lib",
1084
                "matplotlib",
1085
                "tests",
1086
                "test_collections.py::test_EventCollection_nosort",
1087
            )
1088
        ],
1089
        skip_tests=[
1090
            "test__EventCollection__get_segments",
1091
            "test__EventCollection__set_positions",
1092
            "test__EventCollection__add_positions",
1093
            "test__EventCollection__append_positions",
1094
            "test__EventCollection__extend_positions",
1095
            "test__EventCollection__switch_orientation",
1096
            "test__EventCollection__switch_orientation_2x",
1097
            "test__EventCollection__set_orientation",
1098
            "test__EventCollection__set_linelength",
1099
            "test__EventCollection__set_lineoffset",
1100
            "test__EventCollection__set_linestyle",
1101
            "test__EventCollection__set_linestyle_single_dash",
1102
            "test__EventCollection__set_linewidth",
1103
            "test__EventCollection__set_color",
1104
            "test_cap_and_joinstyle_image",
1105
        ],
1106
        unittests=Matplotlib25UnittestGenerator(),
1107
        systemtests=Matplotlib25SystemtestGenerator(),
1108
        api=Matplotlib25API(),
1109
        grammar=grammar_25,
1110
        loc=66180,
1111
    )
1112
    Matplotlib(
1✔
1113
        bug_id=26,
1114
        buggy_commit_id="04d9d28b820af0b4df230a3c67314ed5b0af8fdd",
1115
        fixed_commit_id="557375ff91f64c1b827f6014da2d369513a69316",
1116
        test_files=[Path("lib", "matplotlib", "tests", "test_axes.py")],
1117
        test_cases=[
1118
            os.path.join(
1119
                "lib", "matplotlib", "tests", "test_axes.py::test_set_ticks_inverted"
1120
            )
1121
        ],
1122
        relevant_test_files=[
1123
            os.path.join(
1124
                "lib", "matplotlib", "tests", "test_axes.py::test_set_ticks_inverted"
1125
            ),
1126
            os.path.join(
1127
                "lib", "matplotlib", "tests", "test_axes.py::test_inverted_limits"
1128
            ),
1129
            os.path.join(
1130
                "lib", "matplotlib", "tests", "test_axes.py::test_tick_label_update"
1131
            ),
1132
        ],
1133
        unittests=Matplotlib26UnittestGenerator(),
1134
        systemtests=Matplotlib26SystemtestGenerator(),
1135
        api=Matplotlib26API(),
1136
        grammar=grammar_26,
1137
        loc=65354,
1138
    )
1139
    Matplotlib(
1✔
1140
        bug_id=27,
1141
        buggy_commit_id="11269123d516cda369764a081ddfb8c1a10ddc53",
1142
        fixed_commit_id="02f25d60139b160fd9b321802b4a2f6c6f3f8672",
1143
        test_files=[Path("lib", "matplotlib", "tests", "test_colorbar.py")],
1144
        test_cases=[
1145
            os.path.join(
1146
                "lib", "matplotlib", "tests", "test_colorbar.py::test_colorbar_label"
1147
            )
1148
        ],
1149
        skip_tests=[
1150
            "test_colorbar_positioning",
1151
            "test_colorbar_closed_patch",
1152
        ],
1153
        unittests=Matplotlib27UnittestGenerator(),
1154
        systemtests=Matplotlib27SystemtestGenerator(),
1155
        api=Matplotlib27API(),
1156
        grammar=grammar_27,
1157
        loc=65356,
1158
    )
1159
    Matplotlib(
1✔
1160
        bug_id=28,
1161
        buggy_commit_id="896fb8140a600341ebb5eaa4191584f23df2d2a0",
1162
        fixed_commit_id="94ac78a47cfaed9a09e5fd8295b88e8248b67f55",
1163
        test_files=[Path("lib", "matplotlib", "tests", "test_axes.py")],
1164
        test_cases=[
1165
            os.path.join(
1166
                "lib", "matplotlib", "tests", "test_axes.py::test_log_scales_invalid"
1167
            )
1168
        ],
1169
        unittests=Matplotlib28UnittestGenerator(),
1170
        systemtests=Matplotlib28SystemtestGenerator(),
1171
        api=Matplotlib28API(),
1172
        grammar=grammar_28,
1173
        relevant_test_files=[
1174
            os.path.join(
1175
                "lib", "matplotlib", "tests", "test_axes.py::test_log_scales_invalid"
1176
            ),
1177
            os.path.join(
1178
                "lib", "matplotlib", "tests", "test_axes.py::test_shared_scale"
1179
            ),
1180
            os.path.join(
1181
                "lib", "matplotlib", "tests", "test_axes.py::test_minorticks_on"
1182
            ),
1183
            os.path.join(
1184
                "lib", "matplotlib", "tests", "test_axes.py::test_secondary_minorloc"
1185
            ),
1186
            os.path.join(
1187
                "lib",
1188
                "matplotlib",
1189
                "tests",
1190
                "test_axes.py::test_boxplot_no_weird_whisker",
1191
            ),
1192
        ],
1193
        loc=65357,
1194
    )
1195
    Matplotlib(
1✔
1196
        bug_id=29,
1197
        buggy_commit_id="cdf9e30e4f3fb7747b178ee9c3849dfdebee7bd0",
1198
        fixed_commit_id="fc51b411ba5d0984544ecff97e0a28ea4b6a6d03",
1199
        test_files=[Path("lib", "matplotlib", "tests", "test_axes.py")],
1200
        test_cases=[
1201
            os.path.join(
1202
                "lib", "matplotlib", "tests", "test_axes.py::test_inverted_cla"
1203
            )
1204
        ],
1205
        relevant_test_files=[
1206
            os.path.join(
1207
                "lib", "matplotlib", "tests", "test_axes.py::test_inverted_cla"
1208
            ),
1209
            os.path.join("lib", "matplotlib", "tests", "test_axes.py::test_twinx_cla"),
1210
        ],
1211
        unittests=Matplotlib29UnittestGenerator(),
1212
        systemtests=Matplotlib29SystemtestGenerator(),
1213
        api=Matplotlib29API(),
1214
        grammar=grammar_29,
1215
        loc=66175,
1216
    )
1217
    Matplotlib(
1✔
1218
        bug_id=30,
1219
        buggy_commit_id="3b26ee6f6b31bc0cca4be0407dbb40f44756030d",
1220
        fixed_commit_id="d4de838fe7b38abb02f061540fd93962cc063fc4",
1221
        test_files=[Path("lib", "matplotlib", "tests", "test_colors.py")],
1222
        test_cases=[
1223
            os.path.join(
1224
                "lib",
1225
                "matplotlib",
1226
                "tests",
1227
                "test_colors.py::test_makeMappingArray[1-result2]",
1228
            )
1229
        ],
1230
        relevant_test_files=[
1231
            os.path.join(
1232
                "lib", "matplotlib", "tests", "test_colors.py::test_makeMappingArray"
1233
            )
1234
        ],
1235
        unittests=Matplotlib30UnittestGenerator(),
1236
        systemtests=Matplotlib30SystemtestGenerator(),
1237
        api=Matplotlib30API(),
1238
        grammar=grammar_30,
1239
        loc=66111,
1240
    )
1241

1242

1243
class MatplotlibAPI(API):
1✔
1244
    # Generous timeout: the first rendering call in a fresh environment builds
1245
    # matplotlib's font cache, which under parallel CPU load can take well over
1246
    # the default 10s and would otherwise spuriously time out a passing test.
1247
    def __init__(self, default_timeout: int = 90):
1✔
1248
        super().__init__(default_timeout=default_timeout)
1✔
1249

1250
    def oracle(self, args) -> Tuple[TestResult, str]:
1✔
1251
        return TestResult.UNDEFINED, ""
×
1252

1253

1254
# ======================================================================
1255
# bug_30: ``matplotlib.colors.makeMappingArray`` produced a 2-element
1256
# lookup table for ``N == 1`` (concatenating ``[y1[0]]`` and ``[y0[-1]]``)
1257
# instead of the documented single ``y0[-1]`` value.  The fix special-cases
1258
# ``N == 1`` to return ``np.array(y0[-1])``.
1259
#
1260
# System-test format:  ``<N> <gamma> <x,y0,y1> <x,y0,y1> ...``
1261
#   The harness calls ``makeMappingArray(N, data, gamma)`` and prints the
1262
#   rounded lookup table.  ``N == 1`` triggers the fault; ``N >= 2`` does
1263
#   not.  The oracle recomputes the correct (fixed) table in pure Python.
1264
# ======================================================================
1265

1266

1267
def _correct_make_mapping_array(
1✔
1268
    n: int, data: List[Tuple[float, float, float]], gamma: float
1269
) -> List[float]:
NEW
1270
    xs = [d[0] for d in data]
×
NEW
1271
    y0 = [d[1] for d in data]
×
NEW
1272
    y1 = [d[2] for d in data]
×
NEW
1273
    if n == 1:
×
NEW
1274
        return [min(max(y0[-1], 0.0), 1.0)]
×
NEW
1275
    x = [xi * (n - 1) for xi in xs]
×
NEW
1276
    xind = [(n - 1) * ((i / (n - 1)) ** gamma) for i in range(n)]
×
NEW
1277
    inner = xind[1:-1]
×
NEW
1278
    ind = [bisect.bisect_left(x, v) for v in inner]
×
NEW
1279
    lut = [y1[0]]
×
NEW
1280
    for k, i in enumerate(ind):
×
NEW
1281
        distance = (inner[k] - x[i - 1]) / (x[i] - x[i - 1])
×
NEW
1282
        lut.append(distance * (y0[i] - y1[i - 1]) + y1[i - 1])
×
NEW
1283
    lut.append(y0[-1])
×
NEW
1284
    return [min(max(v, 0.0), 1.0) for v in lut]
×
1285

1286

1287
def _parse_mapping_data(
1✔
1288
    tokens: List[str],
1289
) -> Tuple[int, float, List[Tuple[float, float, float]]]:
NEW
1290
    n = int(tokens[0])
×
NEW
1291
    gamma = float(tokens[1])
×
NEW
1292
    data = []
×
NEW
1293
    for tok in tokens[2:]:
×
NEW
1294
        a, b, c = tok.split(",")
×
NEW
1295
        data.append((float(a), float(b), float(c)))
×
NEW
1296
    return n, gamma, data
×
1297

1298

1299
def _round_list(values: List[float], ndigits: int = 6) -> str:
1✔
NEW
1300
    return str([round(float(v), ndigits) for v in values])
×
1301

1302

1303
class Matplotlib30API(MatplotlibAPI):
1✔
1304
    def oracle(self, args: Any) -> Tuple[TestResult, str]:
1✔
NEW
1305
        if args is None:
×
NEW
1306
            return TestResult.UNDEFINED, "No process finished"
×
NEW
1307
        process: subprocess.CompletedProcess = args
×
NEW
1308
        try:
×
NEW
1309
            n, gamma, data = _parse_mapping_data(list(process.args[2:]))
×
NEW
1310
        except (IndexError, ValueError):
×
NEW
1311
            return TestResult.UNDEFINED, "Malformed test input"
×
NEW
1312
        expected = _round_list(_correct_make_mapping_array(n, data, gamma))
×
NEW
1313
        out = process.stdout.decode("utf8").strip()
×
NEW
1314
        if process.returncode == 0 and out == expected:
×
NEW
1315
            return TestResult.PASSING, f"Expected {expected}"
×
NEW
1316
        return TestResult.FAILING, f"Expected {expected}, but was {out!r}"
×
1317

1318

1319
class Matplotlib30TestGenerator:
1✔
1320
    @staticmethod
1✔
1321
    def generate_data() -> List[Tuple[float, float, float]]:
1✔
NEW
1322
        nseg = random.randint(2, 4)
×
NEW
1323
        interior = sorted(
×
1324
            round(random.uniform(0.15, 0.85), 3) for _ in range(nseg - 2)
1325
        )
NEW
1326
        xs = [0.0] + interior + [1.0]
×
NEW
1327
        return [
×
1328
            (xi, round(random.uniform(0, 1), 3), round(random.uniform(0, 1), 3))
1329
            for xi in xs
1330
        ]
1331

1332
    @staticmethod
1✔
1333
    def format_data(data: List[Tuple[float, float, float]]) -> str:
1✔
NEW
1334
        return " ".join(f"{a},{b},{c}" for a, b, c in data)
×
1335

1336
    def make_failing(self) -> str:
1✔
NEW
1337
        return f"1 1.0 {self.format_data(self.generate_data())}"
×
1338

1339
    def make_passing(self) -> str:
1✔
NEW
1340
        n = random.randint(2, 8)
×
NEW
1341
        return f"{n} 1.0 {self.format_data(self.generate_data())}"
×
1342

1343

1344
class Matplotlib30SystemtestGenerator(
1✔
1345
    SystemtestGenerator, Matplotlib30TestGenerator
1346
):
1347
    def generate_failing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
1348
        return self.make_failing(), TestResult.FAILING
×
1349

1350
    def generate_passing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
1351
        return self.make_passing(), TestResult.PASSING
×
1352

1353

1354
class Matplotlib30UnittestGenerator(
1✔
1355
    python.PythonGenerator, UnittestGenerator, Matplotlib30TestGenerator
1356
):
1357
    def get_imports(self) -> List[ast.stmt]:
1✔
NEW
1358
        return ast.parse(
×
1359
            "import matplotlib\n"
1360
            "matplotlib.use('Agg')\n"
1361
            "import numpy as np\n"
1362
            "import matplotlib.colors as mcolors\n"
1363
        ).body
1364

1365
    @staticmethod
1✔
1366
    def _assert(
1✔
1367
        n: int, data: List[Tuple[float, float, float]], gamma: float
1368
    ) -> List[ast.stmt]:
1369
        # Inline the call so the assertion works as a self-contained test-method
1370
        # body (the framework places all statements inside the TestCase class, so
1371
        # a class-body helper could not be referenced by bare name).
NEW
1372
        expected = [round(v, 6) for v in _correct_make_mapping_array(n, data, gamma)]
×
NEW
1373
        src = (
×
1374
            f"self.assertEqual({expected!r}, "
1375
            "[round(float(v), 6) for v in np.atleast_1d(np.asarray("
1376
            f"mcolors.makeMappingArray({n}, {list(data)!r}, {gamma})))])"
1377
        )
NEW
1378
        return ast.parse(src).body
×
1379

1380
    def generate_failing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
1381
        data = self.generate_data()
×
NEW
1382
        test = self.get_empty_test()
×
NEW
1383
        test.body = self._assert(1, data, 1.0)
×
NEW
1384
        return test, TestResult.FAILING
×
1385

1386
    def generate_passing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
1387
        data = self.generate_data()
×
NEW
1388
        n = random.randint(2, 8)
×
NEW
1389
        test = self.get_empty_test()
×
NEW
1390
        test.body = self._assert(n, data, 1.0)
×
NEW
1391
        return test, TestResult.PASSING
×
1392

1393

1394
grammar_30: Grammar = clean_up(
1✔
1395
    dict(
1396
        {
1397
            "<start>": ["<n> <gamma> <segments>"],
1398
            "<n>": ["<number>"],
1399
            "<gamma>": ["1.0"],
1400
            "<segments>": ["<segment>", "<segment> <segments>"],
1401
            "<segment>": ["<float>,<float>,<float>"],
1402
        },
1403
        **FLOAT,
1404
    )
1405
)
1406

1407
assert is_valid_grammar(grammar_30)
1✔
1408

1409

1410
# ======================================================================
1411
# bug_3: ``matplotlib.markers.MarkerStyle`` initialised ``_filled`` to
1412
# ``True`` unconditionally, so a *filled* marker created with
1413
# ``fillstyle='none'`` still reported ``is_filled() == True``.  The fix
1414
# initialises ``_filled = self._fillstyle != 'none'`` before the marker
1415
# function runs, so an explicitly unfilled marker reports ``False``.
1416
#
1417
# System-test format:  ``<marker> <fillstyle>``
1418
#   The harness builds ``MarkerStyle(marker, fillstyle)`` and prints
1419
#   ``is_filled()``.  For a *filled* marker the correct (fixed) answer is
1420
#   ``fillstyle != 'none'``; ``fillstyle='none'`` triggers the fault
1421
#   (buggy prints ``True``, fixed prints ``False``).
1422
# ======================================================================
1423

1424
_FILLED_MARKERS = ["o", "v", "^", "8", "s", "p", "*", "h", "H", "D", "d"]
1✔
1425

1426

1427
def _correct_is_filled(fillstyle: str) -> bool:
1✔
1428
    # Only ever evaluated for *filled* markers, whose fixed ``is_filled``
1429
    # value is simply ``fillstyle != 'none'``.
NEW
1430
    return fillstyle != "none"
×
1431

1432

1433
class Matplotlib3API(MatplotlibAPI):
1✔
1434
    def oracle(self, args: Any) -> Tuple[TestResult, str]:
1✔
NEW
1435
        if args is None:
×
NEW
1436
            return TestResult.UNDEFINED, "No process finished"
×
NEW
1437
        process: subprocess.CompletedProcess = args
×
NEW
1438
        try:
×
NEW
1439
            fillstyle = process.args[3]
×
NEW
1440
        except (IndexError, ValueError):
×
NEW
1441
            return TestResult.UNDEFINED, "Malformed test input"
×
NEW
1442
        expected = str(_correct_is_filled(fillstyle))
×
NEW
1443
        out = process.stdout.decode("utf8").strip()
×
NEW
1444
        if process.returncode == 0 and out == expected:
×
NEW
1445
            return TestResult.PASSING, f"Expected {expected}"
×
NEW
1446
        return TestResult.FAILING, f"Expected {expected}, but was {out!r}"
×
1447

1448

1449
class Matplotlib3TestGenerator:
1✔
1450
    def make_failing(self) -> str:
1✔
NEW
1451
        return f"{random.choice(_FILLED_MARKERS)} none"
×
1452

1453
    def make_passing(self) -> str:
1✔
NEW
1454
        return f"{random.choice(_FILLED_MARKERS)} full"
×
1455

1456

1457
class Matplotlib3SystemtestGenerator(
1✔
1458
    SystemtestGenerator, Matplotlib3TestGenerator
1459
):
1460
    def generate_failing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
1461
        return self.make_failing(), TestResult.FAILING
×
1462

1463
    def generate_passing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
1464
        return self.make_passing(), TestResult.PASSING
×
1465

1466

1467
class Matplotlib3UnittestGenerator(
1✔
1468
    python.PythonGenerator, UnittestGenerator, Matplotlib3TestGenerator
1469
):
1470
    def get_imports(self) -> List[ast.stmt]:
1✔
NEW
1471
        return ast.parse(
×
1472
            "import matplotlib\n"
1473
            "matplotlib.use('Agg')\n"
1474
            "from matplotlib.markers import MarkerStyle\n"
1475
        ).body
1476

1477
    @staticmethod
1✔
1478
    def _assert(marker: str, fillstyle: str) -> List[ast.stmt]:
1✔
1479
        # Inline the call (the framework places every statement inside the
1480
        # TestCase class, so a class-body helper cannot be referenced by name).
NEW
1481
        expected = _correct_is_filled(fillstyle)
×
NEW
1482
        src = (
×
1483
            f"self.assertEqual({expected!r}, "
1484
            f"bool(MarkerStyle({marker!r}, {fillstyle!r}).is_filled()))"
1485
        )
NEW
1486
        return ast.parse(src).body
×
1487

1488
    def generate_failing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
1489
        test = self.get_empty_test()
×
NEW
1490
        test.body = self._assert(random.choice(_FILLED_MARKERS), "none")
×
NEW
1491
        return test, TestResult.FAILING
×
1492

1493
    def generate_passing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
1494
        test = self.get_empty_test()
×
NEW
1495
        test.body = self._assert(random.choice(_FILLED_MARKERS), "full")
×
NEW
1496
        return test, TestResult.PASSING
×
1497

1498

1499
grammar_3: Grammar = clean_up(
1✔
1500
    {
1501
        "<start>": ["<marker> <fillstyle>"],
1502
        "<marker>": ["o", "v", "^", "8", "s", "p", "*", "h", "H", "D", "d"],
1503
        "<fillstyle>": ["full", "none", "left", "right", "top", "bottom"],
1504
    }
1505
)
1506

1507
assert is_valid_grammar(grammar_3)
1✔
1508

1509

1510
# ======================================================================
1511
# bug_16: ``matplotlib.transforms.nonsingular`` did not cast integer
1512
# endpoints to float, so ``abs(np.int8(-128))`` wrapped to ``-128`` and
1513
# ``vmax - vmin`` could overflow, yielding a wrong expanded interval.  The
1514
# fix inserts ``vmin, vmax = map(float, [vmin, vmax])`` before the
1515
# arithmetic.
1516
#
1517
# System-test format:  ``<dtype> <vmin> <vmax>``  (dtype one of
1518
# int8/int16/int32/int64/float64).  The harness calls ``nonsingular`` on the
1519
# typed endpoints and prints the rounded ``[vmin, vmax]``.  A signed-integer
1520
# dtype whose *vmin* is the most-negative value triggers the fault (``abs``
1521
# overflow); ``float64`` never does.  The oracle recomputes the correct
1522
# (float) result in pure Python.
1523
# ======================================================================
1524

1525
_TINY_FLOAT = 2.2250738585072014e-308  # == np.finfo(float).tiny
1✔
1526

1527
_INT_RANGES = {
1✔
1528
    "int8": (-128, 127),
1529
    "int16": (-32768, 32767),
1530
    "int32": (-2147483648, 2147483647),
1531
    "int64": (-9223372036854775808, 9223372036854775807),
1532
}
1533

1534

1535
def _correct_nonsingular(vmin, vmax, expander=0.001, tiny=1e-15, increasing=True):
1✔
NEW
1536
    import math
×
1537

NEW
1538
    vmin = float(vmin)
×
NEW
1539
    vmax = float(vmax)
×
NEW
1540
    if (not math.isfinite(vmin)) or (not math.isfinite(vmax)):
×
NEW
1541
        return -expander, expander
×
NEW
1542
    swapped = False
×
NEW
1543
    if vmax < vmin:
×
NEW
1544
        vmin, vmax = vmax, vmin
×
NEW
1545
        swapped = True
×
NEW
1546
    maxabsvalue = max(abs(vmin), abs(vmax))
×
NEW
1547
    if maxabsvalue < (1e6 / tiny) * _TINY_FLOAT:
×
NEW
1548
        vmin = -expander
×
NEW
1549
        vmax = expander
×
NEW
1550
    elif vmax - vmin <= maxabsvalue * tiny:
×
NEW
1551
        if vmax == 0 and vmin == 0:
×
NEW
1552
            vmin = -expander
×
NEW
1553
            vmax = expander
×
1554
        else:
NEW
1555
            vmin -= expander * abs(vmin)
×
NEW
1556
            vmax += expander * abs(vmax)
×
NEW
1557
    if swapped and not increasing:
×
NEW
1558
        vmin, vmax = vmax, vmin
×
NEW
1559
    return vmin, vmax
×
1560

1561

1562
class Matplotlib16API(MatplotlibAPI):
1✔
1563
    def oracle(self, args: Any) -> Tuple[TestResult, str]:
1✔
NEW
1564
        if args is None:
×
NEW
1565
            return TestResult.UNDEFINED, "No process finished"
×
NEW
1566
        process: subprocess.CompletedProcess = args
×
NEW
1567
        try:
×
NEW
1568
            vmin = float(process.args[3])
×
NEW
1569
            vmax = float(process.args[4])
×
NEW
1570
        except (IndexError, ValueError):
×
NEW
1571
            return TestResult.UNDEFINED, "Malformed test input"
×
NEW
1572
        expected = str([round(v, 9) for v in _correct_nonsingular(vmin, vmax)])
×
NEW
1573
        out = process.stdout.decode("utf8").strip()
×
NEW
1574
        if process.returncode == 0 and out == expected:
×
NEW
1575
            return TestResult.PASSING, f"Expected {expected}"
×
NEW
1576
        return TestResult.FAILING, f"Expected {expected}, but was {out!r}"
×
1577

1578

1579
class Matplotlib16TestGenerator:
1✔
1580
    def make_failing(self) -> str:
1✔
NEW
1581
        dtype = random.choice(list(_INT_RANGES))
×
NEW
1582
        negmost, _ = _INT_RANGES[dtype]
×
NEW
1583
        vmax = random.choice([negmost, 0])
×
NEW
1584
        return f"{dtype} {negmost} {vmax}"
×
1585

1586
    def make_passing(self) -> str:
1✔
NEW
1587
        lo = round(random.uniform(-100, 50), 3)
×
NEW
1588
        hi = round(lo + random.uniform(1, 50), 3)
×
NEW
1589
        return f"float64 {lo} {hi}"
×
1590

1591

1592
class Matplotlib16SystemtestGenerator(
1✔
1593
    SystemtestGenerator, Matplotlib16TestGenerator
1594
):
1595
    def generate_failing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
1596
        return self.make_failing(), TestResult.FAILING
×
1597

1598
    def generate_passing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
1599
        return self.make_passing(), TestResult.PASSING
×
1600

1601

1602
class Matplotlib16UnittestGenerator(
1✔
1603
    python.PythonGenerator, UnittestGenerator, Matplotlib16TestGenerator
1604
):
1605
    def get_imports(self) -> List[ast.stmt]:
1✔
NEW
1606
        return ast.parse(
×
1607
            "import matplotlib\n"
1608
            "matplotlib.use('Agg')\n"
1609
            "import numpy as np\n"
1610
            "import matplotlib.transforms as mtransforms\n"
1611
        ).body
1612

1613
    @staticmethod
1✔
1614
    def _assert(dtype: str, vmin, vmax) -> List[ast.stmt]:
1✔
NEW
1615
        expected = [
×
1616
            round(v, 9) for v in _correct_nonsingular(float(vmin), float(vmax))
1617
        ]
NEW
1618
        cast = "float" if dtype == "float64" else "int"
×
NEW
1619
        src = (
×
1620
            f"self.assertEqual({expected!r}, "
1621
            f"[round(float(v), 9) for v in mtransforms.nonsingular("
1622
            f"np.{dtype}({cast}({vmin})), np.{dtype}({cast}({vmax})))])"
1623
        )
NEW
1624
        return ast.parse(src).body
×
1625

1626
    def generate_failing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
1627
        dtype = random.choice(list(_INT_RANGES))
×
NEW
1628
        negmost, _ = _INT_RANGES[dtype]
×
NEW
1629
        vmax = random.choice([negmost, 0])
×
NEW
1630
        test = self.get_empty_test()
×
NEW
1631
        test.body = self._assert(dtype, negmost, vmax)
×
NEW
1632
        return test, TestResult.FAILING
×
1633

1634
    def generate_passing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
1635
        lo = round(random.uniform(-100, 50), 3)
×
NEW
1636
        hi = round(lo + random.uniform(1, 50), 3)
×
NEW
1637
        test = self.get_empty_test()
×
NEW
1638
        test.body = self._assert("float64", lo, hi)
×
NEW
1639
        return test, TestResult.PASSING
×
1640

1641

1642
grammar_16: Grammar = clean_up(
1✔
1643
    dict(
1644
        {
1645
            "<start>": ["<dtype> <float> <float>"],
1646
            "<dtype>": ["int8", "int16", "int32", "int64", "float64"],
1647
        },
1648
        **FLOAT,
1649
    )
1650
)
1651

1652
assert is_valid_grammar(grammar_16)
1✔
1653

1654

1655
# bug_17 is the identical ``transforms.nonsingular`` integer-overflow fault as
1656
# bug_16 (a different buggy/fixed commit pair), so it reuses the same oracle,
1657
# generators and grammar.
1658
Matplotlib17API = Matplotlib16API
1✔
1659
Matplotlib17SystemtestGenerator = Matplotlib16SystemtestGenerator
1✔
1660
Matplotlib17UnittestGenerator = Matplotlib16UnittestGenerator
1✔
1661
grammar_17 = grammar_16
1✔
1662

1663

1664
# ======================================================================
1665
# bug_13: ``matplotlib.path.Path.make_compound_path`` concatenated the code
1666
# arrays of its inputs verbatim, leaving *internal* ``STOP`` codes in the
1667
# middle of the compound path.  The fix strips every ``STOP`` code and only
1668
# re-appends a single trailing ``STOP`` if the concatenation originally ended
1669
# with one.
1670
#
1671
# System-test format:  ``<path> <path> ...`` where each path is a comma-
1672
# separated list of integer path codes (0=STOP, 1=MOVETO, 2=LINETO) and every
1673
# path starts with MOVETO.  The harness builds the paths, calls
1674
# ``make_compound_path`` and prints the resulting code list.  An *internal*
1675
# STOP triggers the fault; paths without STOPs never do.  The oracle recomputes
1676
# the correct (STOP-stripped) code list in pure Python.
1677
# ======================================================================
1678

1679
_STOP_CODE = 0
1✔
1680

1681

1682
def _correct_compound_codes_from_lists(code_lists: List[List[int]]) -> List[int]:
1✔
NEW
1683
    all_codes = [c for cs in code_lists for c in cs]
×
NEW
1684
    fixed = [c for c in all_codes if c != _STOP_CODE]
×
NEW
1685
    if all_codes and all_codes[-1] == _STOP_CODE:
×
NEW
1686
        fixed.append(_STOP_CODE)
×
NEW
1687
    return fixed
×
1688

1689

1690
def _correct_compound_codes(tokens: List[str]) -> List[int]:
1✔
NEW
1691
    return _correct_compound_codes_from_lists(
×
1692
        [[int(c) for c in t.split(",")] for t in tokens]
1693
    )
1694

1695

1696
class Matplotlib13API(MatplotlibAPI):
1✔
1697
    def oracle(self, args: Any) -> Tuple[TestResult, str]:
1✔
NEW
1698
        if args is None:
×
NEW
1699
            return TestResult.UNDEFINED, "No process finished"
×
NEW
1700
        process: subprocess.CompletedProcess = args
×
NEW
1701
        try:
×
NEW
1702
            tokens = list(process.args[2:])
×
NEW
1703
            expected = str(_correct_compound_codes(tokens))
×
NEW
1704
        except (IndexError, ValueError):
×
NEW
1705
            return TestResult.UNDEFINED, "Malformed test input"
×
NEW
1706
        out = process.stdout.decode("utf8").strip()
×
NEW
1707
        if process.returncode == 0 and out == expected:
×
NEW
1708
            return TestResult.PASSING, f"Expected {expected}"
×
NEW
1709
        return TestResult.FAILING, f"Expected {expected}, but was {out!r}"
×
1710

1711

1712
class Matplotlib13TestGenerator:
1✔
1713
    @staticmethod
1✔
1714
    def _rand_codes() -> List[int]:
1✔
1715
        # A valid sub-path: starts with MOVETO, then 1-3 MOVETO/LINETO, no STOP.
NEW
1716
        return [1] + [random.choice([1, 2]) for _ in range(random.randint(1, 3))]
×
1717

1718
    def _failing_lists(self) -> List[List[int]]:
1✔
NEW
1719
        lists = [self._rand_codes() + [_STOP_CODE], self._rand_codes()]
×
NEW
1720
        if random.random() < 0.5:
×
NEW
1721
            lists.append(self._rand_codes())
×
NEW
1722
        return lists
×
1723

1724
    def _passing_lists(self) -> List[List[int]]:
1✔
NEW
1725
        return [self._rand_codes() for _ in range(random.randint(2, 3))]
×
1726

1727
    @staticmethod
1✔
1728
    def _format(code_lists: List[List[int]]) -> str:
1✔
NEW
1729
        return " ".join(",".join(str(c) for c in cs) for cs in code_lists)
×
1730

1731
    def make_failing(self) -> str:
1✔
NEW
1732
        return self._format(self._failing_lists())
×
1733

1734
    def make_passing(self) -> str:
1✔
NEW
1735
        return self._format(self._passing_lists())
×
1736

1737

1738
class Matplotlib13SystemtestGenerator(
1✔
1739
    SystemtestGenerator, Matplotlib13TestGenerator
1740
):
1741
    def generate_failing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
1742
        return self.make_failing(), TestResult.FAILING
×
1743

1744
    def generate_passing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
1745
        return self.make_passing(), TestResult.PASSING
×
1746

1747

1748
class Matplotlib13UnittestGenerator(
1✔
1749
    python.PythonGenerator, UnittestGenerator, Matplotlib13TestGenerator
1750
):
1751
    def get_imports(self) -> List[ast.stmt]:
1✔
NEW
1752
        return ast.parse(
×
1753
            "import matplotlib\n"
1754
            "matplotlib.use('Agg')\n"
1755
            "import numpy as np\n"
1756
            "from matplotlib.path import Path\n"
1757
        ).body
1758

1759
    @staticmethod
1✔
1760
    def _assert(code_lists: List[List[int]]) -> List[ast.stmt]:
1✔
NEW
1761
        expected = _correct_compound_codes_from_lists(code_lists)
×
NEW
1762
        src = (
×
1763
            "paths = [Path(np.array([[float(k), float(k)] for k in range(len(cs))], "
1764
            f"dtype=float), cs) for cs in {code_lists!r}]\n"
1765
            f"self.assertEqual({expected!r}, "
1766
            "[int(c) for c in Path.make_compound_path(*paths).codes])\n"
1767
        )
NEW
1768
        return ast.parse(src).body
×
1769

1770
    def generate_failing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
1771
        test = self.get_empty_test()
×
NEW
1772
        test.body = self._assert(self._failing_lists())
×
NEW
1773
        return test, TestResult.FAILING
×
1774

1775
    def generate_passing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
1776
        test = self.get_empty_test()
×
NEW
1777
        test.body = self._assert(self._passing_lists())
×
NEW
1778
        return test, TestResult.PASSING
×
1779

1780

1781
grammar_13: Grammar = clean_up(
1✔
1782
    {
1783
        "<start>": ["<paths>"],
1784
        "<paths>": ["<path>", "<path> <paths>"],
1785
        "<path>": ["<codes>"],
1786
        "<codes>": ["<code>", "<code>,<codes>"],
1787
        "<code>": ["0", "1", "2"],
1788
    }
1789
)
1790

1791
assert is_valid_grammar(grammar_13)
1✔
1792

1793

1794
# ======================================================================
1795
# bug_24: the setter generated by ``matplotlib.axis._make_getset_interval``
1796
# mixed up ``oldmin``/``oldmax`` when *growing* an already **inverted** data
1797
# interval, so growing an inverted interval with values inside it wrongly
1798
# shrank (and re-oriented) it.  The fix uses
1799
# ``max(vmin, vmax, oldmin), min(vmin, vmax, oldmax)`` in the inverted branch.
1800
#
1801
# System-test format:  ``<old_min> <old_max> <new_min> <new_max>``.  The harness
1802
# seeds the axis data interval with (old_min, old_max) (possibly inverted) then
1803
# grows it with (new_min, new_max) and prints the resulting interval.  An
1804
# inverted seed grown with interior values triggers the fault; a normal
1805
# (increasing) seed never does.  The oracle recomputes the correct interval.
1806
# ======================================================================
1807

1808

1809
def _correct_interval(old_min, old_max, new_min, new_max) -> List[float]:
1✔
NEW
1810
    vmin, vmax = new_min, new_max
×
NEW
1811
    oldmin, oldmax = old_min, old_max
×
NEW
1812
    if oldmin < oldmax:
×
NEW
1813
        r = (min(vmin, vmax, oldmin), max(vmin, vmax, oldmax))
×
1814
    else:
NEW
1815
        r = (max(vmin, vmax, oldmin), min(vmin, vmax, oldmax))
×
NEW
1816
    return [round(float(r[0]), 9), round(float(r[1]), 9)]
×
1817

1818

1819
class Matplotlib24API(MatplotlibAPI):
1✔
1820
    def oracle(self, args: Any) -> Tuple[TestResult, str]:
1✔
NEW
1821
        if args is None:
×
NEW
1822
            return TestResult.UNDEFINED, "No process finished"
×
NEW
1823
        process: subprocess.CompletedProcess = args
×
NEW
1824
        try:
×
NEW
1825
            vals = [float(x) for x in process.args[2:6]]
×
NEW
1826
            expected = str(_correct_interval(*vals))
×
NEW
1827
        except (IndexError, ValueError, TypeError):
×
NEW
1828
            return TestResult.UNDEFINED, "Malformed test input"
×
NEW
1829
        out = process.stdout.decode("utf8").strip()
×
NEW
1830
        if process.returncode == 0 and out == expected:
×
NEW
1831
            return TestResult.PASSING, f"Expected {expected}"
×
NEW
1832
        return TestResult.FAILING, f"Expected {expected}, but was {out!r}"
×
1833

1834

1835
class Matplotlib24TestGenerator:
1✔
1836
    @staticmethod
1✔
1837
    def _seed_and_inner() -> Tuple[float, float, float, float]:
1✔
NEW
1838
        lo = round(random.uniform(-50, 40), 3)
×
NEW
1839
        hi = round(lo + random.uniform(2, 40), 3)
×
NEW
1840
        mid = (lo + hi) / 2
×
NEW
1841
        nlo = round(random.uniform(lo + 0.1, mid), 3)
×
NEW
1842
        nhi = round(random.uniform(mid, hi - 0.1), 3)
×
NEW
1843
        return lo, hi, nlo, nhi
×
1844

1845
    def make_failing(self) -> str:
1✔
NEW
1846
        lo, hi, nlo, nhi = self._seed_and_inner()
×
1847
        # inverted seed (old_min > old_max)
NEW
1848
        return f"{hi} {lo} {nlo} {nhi}"
×
1849

1850
    def make_passing(self) -> str:
1✔
NEW
1851
        lo, hi, nlo, nhi = self._seed_and_inner()
×
1852
        # increasing seed (old_min < old_max)
NEW
1853
        return f"{lo} {hi} {nlo} {nhi}"
×
1854

1855

1856
class Matplotlib24SystemtestGenerator(
1✔
1857
    SystemtestGenerator, Matplotlib24TestGenerator
1858
):
1859
    def generate_failing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
1860
        return self.make_failing(), TestResult.FAILING
×
1861

1862
    def generate_passing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
1863
        return self.make_passing(), TestResult.PASSING
×
1864

1865

1866
class Matplotlib24UnittestGenerator(
1✔
1867
    python.PythonGenerator, UnittestGenerator, Matplotlib24TestGenerator
1868
):
1869
    def get_imports(self) -> List[ast.stmt]:
1✔
NEW
1870
        return ast.parse(
×
1871
            "import matplotlib\n"
1872
            "matplotlib.use('Agg')\n"
1873
            "import matplotlib.pyplot as plt\n"
1874
        ).body
1875

1876
    @staticmethod
1✔
1877
    def _assert(old_min, old_max, new_min, new_max) -> List[ast.stmt]:
1✔
NEW
1878
        expected = _correct_interval(old_min, old_max, new_min, new_max)
×
NEW
1879
        src = (
×
1880
            "fig, ax = plt.subplots()\n"
1881
            "axis = ax.xaxis\n"
1882
            f"axis.set_data_interval({old_min}, {old_max}, ignore=True)\n"
1883
            f"axis.set_data_interval({new_min}, {new_max}, ignore=False)\n"
1884
            f"self.assertEqual({expected!r}, "
1885
            "[round(float(v), 9) for v in axis.get_data_interval()])\n"
1886
            "plt.close(fig)\n"
1887
        )
NEW
1888
        return ast.parse(src).body
×
1889

1890
    def generate_failing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
1891
        lo, hi, nlo, nhi = self._seed_and_inner()
×
NEW
1892
        test = self.get_empty_test()
×
NEW
1893
        test.body = self._assert(hi, lo, nlo, nhi)
×
NEW
1894
        return test, TestResult.FAILING
×
1895

1896
    def generate_passing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
1897
        lo, hi, nlo, nhi = self._seed_and_inner()
×
NEW
1898
        test = self.get_empty_test()
×
NEW
1899
        test.body = self._assert(lo, hi, nlo, nhi)
×
NEW
1900
        return test, TestResult.PASSING
×
1901

1902

1903
grammar_24: Grammar = clean_up(
1✔
1904
    dict(
1905
        {"<start>": ["<float> <float> <float> <float>"]},
1906
        **FLOAT,
1907
    )
1908
)
1909

1910
assert is_valid_grammar(grammar_24)
1✔
1911

1912

1913
# bug_26 is the identical ``_make_getset_interval`` inverted-interval fault as
1914
# bug_24 (a different buggy/fixed commit pair), so it reuses the same oracle,
1915
# generators and grammar.
1916
Matplotlib26API = Matplotlib24API
1✔
1917
Matplotlib26SystemtestGenerator = Matplotlib24SystemtestGenerator
1✔
1918
Matplotlib26UnittestGenerator = Matplotlib24UnittestGenerator
1✔
1919
grammar_26 = grammar_24
1✔
1920

1921

1922
# ======================================================================
1923
# bug_25: ``matplotlib.collections.EventCollection.__init__`` sorted the
1924
# *positions* argument **in place** instead of taking a copy, so building an
1925
# ``EventCollection`` from an unsorted array silently reordered the caller's
1926
# array.  The fix copies the positions (``np.array(positions, copy=True)``)
1927
# before sorting, leaving the input untouched.
1928
#
1929
# System-test format:  ``<number> <number> ...`` (the positions).  The harness
1930
# builds an ``EventCollection`` from the array and prints the array *afterwards*.
1931
# The correct (fixed) behaviour is that the input array is unchanged; an
1932
# unsorted input therefore triggers the fault (buggy prints it sorted), while an
1933
# already-sorted input never does.  The oracle recomputes the correct output
1934
# (the input, unchanged).
1935
# ======================================================================
1936

1937

1938
def _round_num_list(values: List[float], ndigits: int = 6) -> str:
1✔
NEW
1939
    return str([round(float(v), ndigits) for v in values])
×
1940

1941

1942
class Matplotlib25API(MatplotlibAPI):
1✔
1943
    def oracle(self, args: Any) -> Tuple[TestResult, str]:
1✔
NEW
1944
        if args is None:
×
NEW
1945
            return TestResult.UNDEFINED, "No process finished"
×
NEW
1946
        process: subprocess.CompletedProcess = args
×
NEW
1947
        try:
×
NEW
1948
            values = [float(x) for x in process.args[2:]]
×
NEW
1949
            if not values:
×
NEW
1950
                raise ValueError("no positions")
×
NEW
1951
        except (IndexError, ValueError):
×
NEW
1952
            return TestResult.UNDEFINED, "Malformed test input"
×
NEW
1953
        expected = _round_num_list(values)
×
NEW
1954
        out = process.stdout.decode("utf8").strip()
×
NEW
1955
        if process.returncode == 0 and out == expected:
×
NEW
1956
            return TestResult.PASSING, f"Expected {expected}"
×
NEW
1957
        return TestResult.FAILING, f"Expected {expected}, but was {out!r}"
×
1958

1959

1960
class Matplotlib25TestGenerator:
1✔
1961
    @staticmethod
1✔
1962
    def _distinct_values(k: int) -> List[float]:
1✔
NEW
1963
        vals = set()
×
NEW
1964
        while len(vals) < k:
×
NEW
1965
            vals.add(round(random.uniform(-50, 50), 3))
×
NEW
1966
        return list(vals)
×
1967

1968
    def _sorted(self) -> List[float]:
1✔
NEW
1969
        return sorted(self._distinct_values(random.randint(3, 6)))
×
1970

1971
    def _unsorted(self) -> List[float]:
1✔
NEW
1972
        vals = self._sorted()
×
1973
        # Guarantee the list is *not* already in ascending order.
NEW
1974
        while True:
×
NEW
1975
            shuffled = vals[:]
×
NEW
1976
            random.shuffle(shuffled)
×
NEW
1977
            if shuffled != sorted(shuffled):
×
NEW
1978
                return shuffled
×
1979

1980
    @staticmethod
1✔
1981
    def _format(values: List[float]) -> str:
1✔
NEW
1982
        return " ".join(str(v) for v in values)
×
1983

1984
    def make_failing(self) -> str:
1✔
NEW
1985
        return self._format(self._unsorted())
×
1986

1987
    def make_passing(self) -> str:
1✔
NEW
1988
        return self._format(self._sorted())
×
1989

1990

1991
class Matplotlib25SystemtestGenerator(
1✔
1992
    SystemtestGenerator, Matplotlib25TestGenerator
1993
):
1994
    def generate_failing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
1995
        return self.make_failing(), TestResult.FAILING
×
1996

1997
    def generate_passing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
1998
        return self.make_passing(), TestResult.PASSING
×
1999

2000

2001
class Matplotlib25UnittestGenerator(
1✔
2002
    python.PythonGenerator, UnittestGenerator, Matplotlib25TestGenerator
2003
):
2004
    def get_imports(self) -> List[ast.stmt]:
1✔
NEW
2005
        return ast.parse(
×
2006
            "import matplotlib\n"
2007
            "matplotlib.use('Agg')\n"
2008
            "import numpy as np\n"
2009
            "from matplotlib.collections import EventCollection\n"
2010
        ).body
2011

2012
    @staticmethod
1✔
2013
    def _assert(values: List[float]) -> List[ast.stmt]:
1✔
NEW
2014
        expected = [round(float(v), 6) for v in values]
×
NEW
2015
        src = (
×
2016
            f"arr = np.array({list(values)!r}, dtype=float)\n"
2017
            "EventCollection(arr)\n"
2018
            f"self.assertEqual({expected!r}, "
2019
            "[round(float(v), 6) for v in arr])\n"
2020
        )
NEW
2021
        return ast.parse(src).body
×
2022

2023
    def generate_failing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
2024
        test = self.get_empty_test()
×
NEW
2025
        test.body = self._assert(self._unsorted())
×
NEW
2026
        return test, TestResult.FAILING
×
2027

2028
    def generate_passing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
2029
        test = self.get_empty_test()
×
NEW
2030
        test.body = self._assert(self._sorted())
×
NEW
2031
        return test, TestResult.PASSING
×
2032

2033

2034
grammar_25: Grammar = clean_up(
1✔
2035
    dict(
2036
        {
2037
            "<start>": ["<numbers>"],
2038
            "<numbers>": ["<float>", "<float> <numbers>"],
2039
        },
2040
        **FLOAT,
2041
    )
2042
)
2043

2044
assert is_valid_grammar(grammar_25)
1✔
2045

2046

2047
# ======================================================================
2048
# bug_20: ``FigureCanvasBase.inaxes`` returned the topmost axes whose patch
2049
# contained the point *without* checking whether that axes was visible, so an
2050
# invisible axes still "captured" events.  The fix adds ``and a.get_visible()``
2051
# to the filter.
2052
#
2053
# System-test format:  ``<visible> <x> <y>`` where ``<visible>`` is ``true`` or
2054
# ``false`` and ``(x, y)`` is a pixel point *inside* the default single-subplot
2055
# axes.  The harness builds the figure, applies the visibility, and prints
2056
# ``inaxes((x, y)) is None``.  The correct (fixed) answer is ``True`` exactly
2057
# when the axes is invisible; an invisible axes therefore triggers the fault
2058
# (buggy still returns the axes), a visible one never does.
2059
# ======================================================================
2060

2061

2062
class Matplotlib20API(MatplotlibAPI):
1✔
2063
    def oracle(self, args: Any) -> Tuple[TestResult, str]:
1✔
NEW
2064
        if args is None:
×
NEW
2065
            return TestResult.UNDEFINED, "No process finished"
×
NEW
2066
        process: subprocess.CompletedProcess = args
×
NEW
2067
        try:
×
NEW
2068
            visible = process.args[2]
×
NEW
2069
            if visible not in ("true", "false"):
×
NEW
2070
                raise ValueError("bad visible flag")
×
NEW
2071
        except (IndexError, ValueError):
×
NEW
2072
            return TestResult.UNDEFINED, "Malformed test input"
×
NEW
2073
        expected = str(visible == "false")
×
NEW
2074
        out = process.stdout.decode("utf8").strip()
×
NEW
2075
        if process.returncode == 0 and out == expected:
×
NEW
2076
            return TestResult.PASSING, f"Expected {expected}"
×
NEW
2077
        return TestResult.FAILING, f"Expected {expected}, but was {out!r}"
×
2078

2079

2080
class Matplotlib20TestGenerator:
1✔
2081
    @staticmethod
1✔
2082
    def _point() -> Tuple[float, float]:
1✔
2083
        # Well inside the default single-subplot axes (x in [80, 576],
2084
        # y in [52.8, 422.4] at 640x480).
NEW
2085
        return (
×
2086
            round(random.uniform(100, 560), 1),
2087
            round(random.uniform(70, 410), 1),
2088
        )
2089

2090
    def make_failing(self) -> str:
1✔
NEW
2091
        x, y = self._point()
×
NEW
2092
        return f"false {x} {y}"
×
2093

2094
    def make_passing(self) -> str:
1✔
NEW
2095
        x, y = self._point()
×
NEW
2096
        return f"true {x} {y}"
×
2097

2098

2099
class Matplotlib20SystemtestGenerator(
1✔
2100
    SystemtestGenerator, Matplotlib20TestGenerator
2101
):
2102
    def generate_failing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
2103
        return self.make_failing(), TestResult.FAILING
×
2104

2105
    def generate_passing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
2106
        return self.make_passing(), TestResult.PASSING
×
2107

2108

2109
class Matplotlib20UnittestGenerator(
1✔
2110
    python.PythonGenerator, UnittestGenerator, Matplotlib20TestGenerator
2111
):
2112
    def get_imports(self) -> List[ast.stmt]:
1✔
NEW
2113
        return ast.parse(
×
2114
            "import matplotlib\n"
2115
            "matplotlib.use('Agg')\n"
2116
            "import matplotlib.pyplot as plt\n"
2117
        ).body
2118

2119
    @staticmethod
1✔
2120
    def _assert(visible: bool, x: float, y: float) -> List[ast.stmt]:
1✔
NEW
2121
        expected = not visible
×
NEW
2122
        vis_line = "" if visible else "ax.set_visible(False)\n"
×
NEW
2123
        src = (
×
2124
            "fig, ax = plt.subplots()\n"
2125
            f"{vis_line}"
2126
            f"self.assertEqual({expected!r}, "
2127
            f"fig.canvas.inaxes(({x}, {y})) is None)\n"
2128
            "plt.close(fig)\n"
2129
        )
NEW
2130
        return ast.parse(src).body
×
2131

2132
    def generate_failing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
2133
        x, y = self._point()
×
NEW
2134
        test = self.get_empty_test()
×
NEW
2135
        test.body = self._assert(False, x, y)
×
NEW
2136
        return test, TestResult.FAILING
×
2137

2138
    def generate_passing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
2139
        x, y = self._point()
×
NEW
2140
        test = self.get_empty_test()
×
NEW
2141
        test.body = self._assert(True, x, y)
×
NEW
2142
        return test, TestResult.PASSING
×
2143

2144

2145
grammar_20: Grammar = clean_up(
1✔
2146
    dict(
2147
        {
2148
            "<start>": ["<visible> <float> <float>"],
2149
            "<visible>": ["true", "false"],
2150
        },
2151
        **FLOAT,
2152
    )
2153
)
2154

2155
assert is_valid_grammar(grammar_20)
1✔
2156

2157

2158
# ======================================================================
2159
# bug_10: turning a tick's labels on/off with ``Axis.set_tick_params`` did not
2160
# propagate to the axis *offset text* (the ``1e9`` exponent label), so hiding
2161
# every tick label still left the offset text visible.  The fix updates
2162
# ``offsetText`` visibility from ``label1On``/``label2On`` whenever they change.
2163
#
2164
# System-test format:  ``<axis> <label1On> <label2On> <exp> <npts>`` where
2165
# ``<axis>`` is ``x``/``y``, the two label flags are ``true``/``false``, ``exp``
2166
# is the data's power-of-ten magnitude and ``npts`` the point count.  The
2167
# harness plots large data, applies the tick params and prints the offset text's
2168
# visibility.  The correct (fixed) visibility is ``label1On or label2On``;
2169
# hiding both labels triggers the fault (buggy keeps the offset visible).
2170
# ======================================================================
2171

2172

2173
class Matplotlib10API(MatplotlibAPI):
1✔
2174
    def oracle(self, args: Any) -> Tuple[TestResult, str]:
1✔
NEW
2175
        if args is None:
×
NEW
2176
            return TestResult.UNDEFINED, "No process finished"
×
NEW
2177
        process: subprocess.CompletedProcess = args
×
NEW
2178
        try:
×
NEW
2179
            b1 = process.args[3]
×
NEW
2180
            b2 = process.args[4]
×
NEW
2181
            if b1 not in ("true", "false") or b2 not in ("true", "false"):
×
NEW
2182
                raise ValueError("bad flags")
×
NEW
2183
        except (IndexError, ValueError):
×
NEW
2184
            return TestResult.UNDEFINED, "Malformed test input"
×
NEW
2185
        expected = str(b1 == "true" or b2 == "true")
×
NEW
2186
        out = process.stdout.decode("utf8").strip()
×
NEW
2187
        if process.returncode == 0 and out == expected:
×
NEW
2188
            return TestResult.PASSING, f"Expected {expected}"
×
NEW
2189
        return TestResult.FAILING, f"Expected {expected}, but was {out!r}"
×
2190

2191

2192
class Matplotlib10TestGenerator:
1✔
2193
    @staticmethod
1✔
2194
    def _axis() -> str:
1✔
NEW
2195
        return random.choice(["x", "y"])
×
2196

2197
    @staticmethod
1✔
2198
    def _exp() -> int:
1✔
NEW
2199
        return random.randint(7, 15)
×
2200

2201
    @staticmethod
1✔
2202
    def _npts() -> int:
1✔
NEW
2203
        return random.randint(3, 6)
×
2204

2205
    def make_failing(self) -> str:
1✔
NEW
2206
        return f"{self._axis()} false false {self._exp()} {self._npts()}"
×
2207

2208
    def make_passing(self) -> str:
1✔
NEW
2209
        b1, b2 = random.choice([("true", "false"), ("false", "true"),
×
2210
                                ("true", "true")])
NEW
2211
        return f"{self._axis()} {b1} {b2} {self._exp()} {self._npts()}"
×
2212

2213

2214
class Matplotlib10SystemtestGenerator(
1✔
2215
    SystemtestGenerator, Matplotlib10TestGenerator
2216
):
2217
    def generate_failing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
2218
        return self.make_failing(), TestResult.FAILING
×
2219

2220
    def generate_passing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
2221
        return self.make_passing(), TestResult.PASSING
×
2222

2223

2224
class Matplotlib10UnittestGenerator(
1✔
2225
    python.PythonGenerator, UnittestGenerator, Matplotlib10TestGenerator
2226
):
2227
    def get_imports(self) -> List[ast.stmt]:
1✔
NEW
2228
        return ast.parse(
×
2229
            "import matplotlib\n"
2230
            "matplotlib.use('Agg')\n"
2231
            "import matplotlib.pyplot as plt\n"
2232
        ).body
2233

2234
    @staticmethod
1✔
2235
    def _assert(axis: str, b1: bool, b2: bool, exp: int, npts: int) -> List[ast.stmt]:
1✔
NEW
2236
        expected = b1 or b2
×
NEW
2237
        plot = (
×
2238
            "ax.plot(data, [0] * len(data))\n" if axis == "x" else "ax.plot(data)\n"
2239
        )
NEW
2240
        which = "ax.xaxis" if axis == "x" else "ax.yaxis"
×
NEW
2241
        src = (
×
2242
            "fig = plt.figure()\n"
2243
            "ax = fig.add_subplot(1, 1, 1)\n"
2244
            f"data = [(1.01 + 0.01 * i) * 10 ** {exp} for i in range({npts})]\n"
2245
            f"{plot}"
2246
            f"axis = {which}\n"
2247
            f"axis.set_tick_params(label1On={b1!r}, label2On={b2!r})\n"
2248
            f"self.assertEqual({expected!r}, axis.get_offset_text().get_visible())\n"
2249
            "plt.close(fig)\n"
2250
        )
NEW
2251
        return ast.parse(src).body
×
2252

2253
    def generate_failing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
2254
        test = self.get_empty_test()
×
NEW
2255
        test.body = self._assert(self._axis(), False, False, self._exp(), self._npts())
×
NEW
2256
        return test, TestResult.FAILING
×
2257

2258
    def generate_passing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
2259
        b1, b2 = random.choice([(True, False), (False, True), (True, True)])
×
NEW
2260
        test = self.get_empty_test()
×
NEW
2261
        test.body = self._assert(self._axis(), b1, b2, self._exp(), self._npts())
×
NEW
2262
        return test, TestResult.PASSING
×
2263

2264

2265
grammar_10: Grammar = clean_up(
1✔
2266
    dict(
2267
        {
2268
            "<start>": ["<axis> <flag> <flag> <number> <number>"],
2269
            "<axis>": ["x", "y"],
2270
            "<flag>": ["true", "false"],
2271
        },
2272
        **NUMBER,
2273
    )
2274
)
2275

2276
assert is_valid_grammar(grammar_10)
1✔
2277

2278

2279
# ======================================================================
2280
# bug_11: ``Text.get_window_extent(dpi=...)`` temporarily set ``figure.dpi`` to
2281
# the requested value but, for *empty* text, returned early without restoring
2282
# it, permanently corrupting the figure's dpi.  The fix uses a
2283
# ``cbook._setattr_cm`` context manager so the dpi is always restored.
2284
#
2285
# System-test format:  ``<mult> <textkind>`` where ``<mult>`` is the dpi
2286
# multiplier and ``<textkind>`` is the literal ``EMPTY`` (empty label) or a
2287
# word (non-empty label).  The harness draws the label, calls
2288
# ``get_window_extent(dpi=dpi*mult)`` and prints whether ``figure.dpi`` is
2289
# unchanged.  The correct (fixed) answer is always ``True``; an *empty* label
2290
# triggers the fault (buggy leaves dpi corrupted -> ``False``).
2291
# ======================================================================
2292

2293

2294
class Matplotlib11API(MatplotlibAPI):
1✔
2295
    def oracle(self, args: Any) -> Tuple[TestResult, str]:
1✔
NEW
2296
        if args is None:
×
NEW
2297
            return TestResult.UNDEFINED, "No process finished"
×
NEW
2298
        process: subprocess.CompletedProcess = args
×
NEW
2299
        try:
×
NEW
2300
            float(process.args[2])
×
NEW
2301
            _ = process.args[3]
×
NEW
2302
        except (IndexError, ValueError):
×
NEW
2303
            return TestResult.UNDEFINED, "Malformed test input"
×
NEW
2304
        expected = "True"  # fixed always leaves figure.dpi unchanged
×
NEW
2305
        out = process.stdout.decode("utf8").strip()
×
NEW
2306
        if process.returncode == 0 and out == expected:
×
NEW
2307
            return TestResult.PASSING, f"Expected {expected}"
×
NEW
2308
        return TestResult.FAILING, f"Expected {expected}, but was {out!r}"
×
2309

2310

2311
class Matplotlib11TestGenerator:
1✔
2312
    @staticmethod
1✔
2313
    def _mult() -> float:
1✔
NEW
2314
        return round(random.uniform(2.0, 12.0), 3)
×
2315

2316
    @staticmethod
1✔
2317
    def _word() -> str:
1✔
NEW
2318
        return "".join(
×
2319
            random.choice(string.ascii_lowercase)
2320
            for _ in range(random.randint(3, 8))
2321
        )
2322

2323
    def make_failing(self) -> str:
1✔
NEW
2324
        return f"{self._mult()} EMPTY"
×
2325

2326
    def make_passing(self) -> str:
1✔
NEW
2327
        return f"{self._mult()} {self._word()}"
×
2328

2329

2330
class Matplotlib11SystemtestGenerator(
1✔
2331
    SystemtestGenerator, Matplotlib11TestGenerator
2332
):
2333
    def generate_failing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
2334
        return self.make_failing(), TestResult.FAILING
×
2335

2336
    def generate_passing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
2337
        return self.make_passing(), TestResult.PASSING
×
2338

2339

2340
class Matplotlib11UnittestGenerator(
1✔
2341
    python.PythonGenerator, UnittestGenerator, Matplotlib11TestGenerator
2342
):
2343
    def get_imports(self) -> List[ast.stmt]:
1✔
NEW
2344
        return ast.parse(
×
2345
            "import matplotlib\n"
2346
            "matplotlib.use('Agg')\n"
2347
            "import matplotlib.pyplot as plt\n"
2348
        ).body
2349

2350
    @staticmethod
1✔
2351
    def _assert(mult: float, text: str) -> List[ast.stmt]:
1✔
NEW
2352
        src = (
×
2353
            "fig, ax = plt.subplots()\n"
2354
            f"t1 = ax.text(0.5, 0.5, {text!r}, ha='left', va='bottom')\n"
2355
            "fig.canvas.draw()\n"
2356
            "dpi = fig.dpi\n"
2357
            "t1.get_window_extent()\n"
2358
            f"t1.get_window_extent(dpi=dpi * {mult})\n"
2359
            "self.assertEqual(True, fig.dpi == dpi)\n"
2360
            "plt.close(fig)\n"
2361
        )
NEW
2362
        return ast.parse(src).body
×
2363

2364
    def generate_failing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
2365
        test = self.get_empty_test()
×
NEW
2366
        test.body = self._assert(self._mult(), "")
×
NEW
2367
        return test, TestResult.FAILING
×
2368

2369
    def generate_passing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
2370
        test = self.get_empty_test()
×
NEW
2371
        test.body = self._assert(self._mult(), self._word())
×
NEW
2372
        return test, TestResult.PASSING
×
2373

2374

2375
grammar_11: Grammar = clean_up(
1✔
2376
    dict(
2377
        {
2378
            "<start>": ["<float> <textkind>"],
2379
            "<textkind>": ["EMPTY", "<letters>"],
2380
            "<letters>": ["<letter>", "<letter><letters>"],
2381
            "<letter>": srange(string.ascii_lowercase),
2382
        },
2383
        **FLOAT,
2384
    )
2385
)
2386

2387
assert is_valid_grammar(grammar_11)
1✔
2388

2389

2390
# ======================================================================
2391
# bug_14: ``Text.update`` applied the ``fontproperties`` kwarg *after* the
2392
# other kwargs, so an explicit ``size=`` (or other font kwarg) passed *before*
2393
# ``fontproperties`` in the call was overwritten by the fontproperties'
2394
# defaults.  The fix pops and applies ``fontproperties`` *first* so explicit
2395
# kwargs always win.
2396
#
2397
# System-test format:  ``<order> <size> <family>`` where ``<order>`` is ``sf``
2398
# (``size`` kwarg before ``fontproperties``) or ``fs`` (after) and ``<family>``
2399
# is a font family.  The harness sets an axis label with that kwarg order and
2400
# prints the resulting font size.  The correct (fixed) size is always the
2401
# explicit ``size``; the ``sf`` order triggers the fault (buggy resets the size
2402
# to the fontproperties' default of 10).
2403
# ======================================================================
2404

2405
# Single-word families only: a hyphenated string such as ``sans-serif`` is
2406
# (mis)parsed as a fontconfig pattern by ``FontProperties`` and raises.
2407
_FONT_FAMILIES = ["serif", "monospace", "cursive", "fantasy"]
1✔
2408

2409

2410
class Matplotlib14API(MatplotlibAPI):
1✔
2411
    def oracle(self, args: Any) -> Tuple[TestResult, str]:
1✔
NEW
2412
        if args is None:
×
NEW
2413
            return TestResult.UNDEFINED, "No process finished"
×
NEW
2414
        process: subprocess.CompletedProcess = args
×
NEW
2415
        try:
×
NEW
2416
            order = process.args[2]
×
NEW
2417
            size = float(process.args[3])
×
NEW
2418
            if order not in ("sf", "fs"):
×
NEW
2419
                raise ValueError("bad order")
×
NEW
2420
        except (IndexError, ValueError):
×
NEW
2421
            return TestResult.UNDEFINED, "Malformed test input"
×
NEW
2422
        expected = str(float(size))
×
NEW
2423
        out = process.stdout.decode("utf8").strip()
×
NEW
2424
        if process.returncode == 0 and out == expected:
×
NEW
2425
            return TestResult.PASSING, f"Expected {expected}"
×
NEW
2426
        return TestResult.FAILING, f"Expected {expected}, but was {out!r}"
×
2427

2428

2429
class Matplotlib14TestGenerator:
1✔
2430
    @staticmethod
1✔
2431
    def _size() -> float:
1✔
2432
        # Any size != 10.0 (the fontproperties default the buggy code falls to).
NEW
2433
        return round(random.uniform(15.0, 60.0), 1)
×
2434

2435
    @staticmethod
1✔
2436
    def _family() -> str:
1✔
NEW
2437
        return random.choice(_FONT_FAMILIES)
×
2438

2439
    def make_failing(self) -> str:
1✔
NEW
2440
        return f"sf {self._size()} {self._family()}"
×
2441

2442
    def make_passing(self) -> str:
1✔
NEW
2443
        return f"fs {self._size()} {self._family()}"
×
2444

2445

2446
class Matplotlib14SystemtestGenerator(
1✔
2447
    SystemtestGenerator, Matplotlib14TestGenerator
2448
):
2449
    def generate_failing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
2450
        return self.make_failing(), TestResult.FAILING
×
2451

2452
    def generate_passing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
2453
        return self.make_passing(), TestResult.PASSING
×
2454

2455

2456
class Matplotlib14UnittestGenerator(
1✔
2457
    python.PythonGenerator, UnittestGenerator, Matplotlib14TestGenerator
2458
):
2459
    def get_imports(self) -> List[ast.stmt]:
1✔
NEW
2460
        return ast.parse(
×
2461
            "import matplotlib\n"
2462
            "matplotlib.use('Agg')\n"
2463
            "import matplotlib.pyplot as plt\n"
2464
        ).body
2465

2466
    @staticmethod
1✔
2467
    def _assert(order: str, size: float, family: str) -> List[ast.stmt]:
1✔
NEW
2468
        if order == "sf":
×
NEW
2469
            call = (
×
2470
                f"ax.set_xlabel('value', size={size}, fontproperties={family!r})"
2471
            )
2472
        else:
NEW
2473
            call = (
×
2474
                f"ax.set_xlabel('value', fontproperties={family!r}, size={size})"
2475
            )
NEW
2476
        src = (
×
2477
            "fig, ax = plt.subplots()\n"
2478
            f"t = {call}\n"
2479
            f"self.assertEqual({float(size)!r}, t.get_size())\n"
2480
            "plt.close(fig)\n"
2481
        )
NEW
2482
        return ast.parse(src).body
×
2483

2484
    def generate_failing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
2485
        test = self.get_empty_test()
×
NEW
2486
        test.body = self._assert("sf", self._size(), self._family())
×
NEW
2487
        return test, TestResult.FAILING
×
2488

2489
    def generate_passing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
2490
        test = self.get_empty_test()
×
NEW
2491
        test.body = self._assert("fs", self._size(), self._family())
×
NEW
2492
        return test, TestResult.PASSING
×
2493

2494

2495
grammar_14: Grammar = clean_up(
1✔
2496
    dict(
2497
        {
2498
            "<start>": ["<order> <float> <family>"],
2499
            "<order>": ["sf", "fs"],
2500
            "<family>": ["serif", "monospace", "cursive", "fantasy"],
2501
        },
2502
        **FLOAT,
2503
    )
2504
)
2505

2506
assert is_valid_grammar(grammar_14)
1✔
2507

2508

2509
# ======================================================================
2510
# bug_2: ``Axes.scatter`` with an *unfilled* marker forced ``edgecolors='face'``
2511
# and still passed the colors as ``facecolors``, so the points were drawn
2512
# filled.  The fix sets ``facecolors='none'`` and routes the colors to
2513
# ``edgecolors`` for unfilled markers, leaving them hollow.
2514
#
2515
# System-test format:  ``<fillstyle> <n>`` where ``<fillstyle>`` is ``none``
2516
# (unfilled 'o') or ``full`` (filled) and ``n`` is the number of points.  The
2517
# harness scatters ``n`` points with ``n`` distinct colors and prints the number
2518
# of facecolor rows.  The correct (fixed) count is ``0`` for an unfilled marker
2519
# and ``n`` for a filled one; ``fillstyle='none'`` triggers the fault (buggy
2520
# still stores ``n`` facecolors).
2521
# ======================================================================
2522

2523

2524
class Matplotlib2API(MatplotlibAPI):
1✔
2525
    def oracle(self, args: Any) -> Tuple[TestResult, str]:
1✔
NEW
2526
        if args is None:
×
NEW
2527
            return TestResult.UNDEFINED, "No process finished"
×
NEW
2528
        process: subprocess.CompletedProcess = args
×
NEW
2529
        try:
×
NEW
2530
            fillstyle = process.args[2]
×
NEW
2531
            n = int(process.args[3])
×
NEW
2532
            if fillstyle not in ("none", "full"):
×
NEW
2533
                raise ValueError("bad fillstyle")
×
NEW
2534
        except (IndexError, ValueError):
×
NEW
2535
            return TestResult.UNDEFINED, "Malformed test input"
×
NEW
2536
        expected = str(0 if fillstyle == "none" else n)
×
NEW
2537
        out = process.stdout.decode("utf8").strip()
×
NEW
2538
        if process.returncode == 0 and out == expected:
×
NEW
2539
            return TestResult.PASSING, f"Expected {expected}"
×
NEW
2540
        return TestResult.FAILING, f"Expected {expected}, but was {out!r}"
×
2541

2542

2543
class Matplotlib2TestGenerator:
1✔
2544
    @staticmethod
1✔
2545
    def _n() -> int:
1✔
NEW
2546
        return random.randint(2, 16)
×
2547

2548
    def make_failing(self) -> str:
1✔
NEW
2549
        return f"none {self._n()}"
×
2550

2551
    def make_passing(self) -> str:
1✔
NEW
2552
        return f"full {self._n()}"
×
2553

2554

2555
class Matplotlib2SystemtestGenerator(
1✔
2556
    SystemtestGenerator, Matplotlib2TestGenerator
2557
):
2558
    def generate_failing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
2559
        return self.make_failing(), TestResult.FAILING
×
2560

2561
    def generate_passing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
2562
        return self.make_passing(), TestResult.PASSING
×
2563

2564

2565
class Matplotlib2UnittestGenerator(
1✔
2566
    python.PythonGenerator, UnittestGenerator, Matplotlib2TestGenerator
2567
):
2568
    def get_imports(self) -> List[ast.stmt]:
1✔
NEW
2569
        return ast.parse(
×
2570
            "import matplotlib\n"
2571
            "matplotlib.use('Agg')\n"
2572
            "import matplotlib.pyplot as plt\n"
2573
            "from matplotlib.markers import MarkerStyle\n"
2574
        ).body
2575

2576
    @staticmethod
1✔
2577
    def _assert(fillstyle: str, n: int) -> List[ast.stmt]:
1✔
NEW
2578
        expected = 0 if fillstyle == "none" else n
×
NEW
2579
        src = (
×
2580
            f"grays = [round((i + 1) / ({n} + 1), 3) for i in range({n})]\n"
2581
            f"coll = plt.scatter(range({n}), range({n}), "
2582
            "c=[str(g) for g in grays], "
2583
            f"marker=MarkerStyle('o', fillstyle={fillstyle!r}), "
2584
            f"linewidths=[1.0] * {n})\n"
2585
            f"self.assertEqual({expected!r}, coll.get_facecolors().shape[0])\n"
2586
            "plt.close('all')\n"
2587
        )
NEW
2588
        return ast.parse(src).body
×
2589

2590
    def generate_failing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
2591
        test = self.get_empty_test()
×
NEW
2592
        test.body = self._assert("none", self._n())
×
NEW
2593
        return test, TestResult.FAILING
×
2594

2595
    def generate_passing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
2596
        test = self.get_empty_test()
×
NEW
2597
        test.body = self._assert("full", self._n())
×
NEW
2598
        return test, TestResult.PASSING
×
2599

2600

2601
grammar_2: Grammar = clean_up(
1✔
2602
    dict(
2603
        {
2604
            "<start>": ["<fillstyle> <number>"],
2605
            "<fillstyle>": ["none", "full"],
2606
        },
2607
        **NUMBER,
2608
    )
2609
)
2610

2611
assert is_valid_grammar(grammar_2)
1✔
2612

2613

2614
# ======================================================================
2615
# bug_5: ``Axes.scatter`` with an *unfilled* marker overwrote the caller's
2616
# ``linewidths`` with ``rcParams['lines.linewidth']`` unconditionally, so an
2617
# explicit ``linewidths=`` was ignored.  The fix only falls back to the rcParam
2618
# when ``linewidths`` is ``None``.
2619
#
2620
# System-test format:  ``<marker> <lw>`` where ``<marker>`` is an *unfilled*
2621
# marker (e.g. ``x``, ``+``) or a *filled* one (e.g. ``o``, ``s``) and ``<lw>``
2622
# is the requested line width (never the ``1.5`` default).  The harness scatters
2623
# points with that marker and ``linewidths=lw`` and prints the resulting line
2624
# width.  The correct (fixed) width is always ``lw``; an unfilled marker
2625
# triggers the fault (buggy reports ``1.5``).
2626
# ======================================================================
2627

2628
_UNFILLED_MARKERS = ["x", "+", "1", "2", "3", "4"]
1✔
2629
_FILLED_MARKERS2 = ["o", "s", "^", "D", "v", "p"]
1✔
2630

2631

2632
class Matplotlib5API(MatplotlibAPI):
1✔
2633
    def oracle(self, args: Any) -> Tuple[TestResult, str]:
1✔
NEW
2634
        if args is None:
×
NEW
2635
            return TestResult.UNDEFINED, "No process finished"
×
NEW
2636
        process: subprocess.CompletedProcess = args
×
NEW
2637
        try:
×
NEW
2638
            _ = process.args[2]
×
NEW
2639
            lw = float(process.args[3])
×
NEW
2640
        except (IndexError, ValueError):
×
NEW
2641
            return TestResult.UNDEFINED, "Malformed test input"
×
NEW
2642
        expected = str(float(lw))
×
NEW
2643
        out = process.stdout.decode("utf8").strip()
×
NEW
2644
        if process.returncode == 0 and out == expected:
×
NEW
2645
            return TestResult.PASSING, f"Expected {expected}"
×
NEW
2646
        return TestResult.FAILING, f"Expected {expected}, but was {out!r}"
×
2647

2648

2649
class Matplotlib5TestGenerator:
1✔
2650
    @staticmethod
1✔
2651
    def _lw() -> float:
1✔
2652
        # Any width other than the 1.5 rcParams default.
NEW
2653
        return round(random.uniform(2.0, 9.0), 1)
×
2654

2655
    def make_failing(self) -> str:
1✔
NEW
2656
        return f"{random.choice(_UNFILLED_MARKERS)} {self._lw()}"
×
2657

2658
    def make_passing(self) -> str:
1✔
NEW
2659
        return f"{random.choice(_FILLED_MARKERS2)} {self._lw()}"
×
2660

2661

2662
class Matplotlib5SystemtestGenerator(
1✔
2663
    SystemtestGenerator, Matplotlib5TestGenerator
2664
):
2665
    def generate_failing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
2666
        return self.make_failing(), TestResult.FAILING
×
2667

2668
    def generate_passing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
2669
        return self.make_passing(), TestResult.PASSING
×
2670

2671

2672
class Matplotlib5UnittestGenerator(
1✔
2673
    python.PythonGenerator, UnittestGenerator, Matplotlib5TestGenerator
2674
):
2675
    def get_imports(self) -> List[ast.stmt]:
1✔
NEW
2676
        return ast.parse(
×
2677
            "import matplotlib\n"
2678
            "matplotlib.use('Agg')\n"
2679
            "import matplotlib.pyplot as plt\n"
2680
        ).body
2681

2682
    @staticmethod
1✔
2683
    def _assert(marker: str, lw: float) -> List[ast.stmt]:
1✔
NEW
2684
        src = (
×
2685
            "fig, ax = plt.subplots()\n"
2686
            f"pc = ax.scatter(range(5), [0] * 5, c='C0', marker={marker!r}, "
2687
            f"s=100, linewidths={lw})\n"
2688
            f"self.assertEqual({float(lw)!r}, float(pc.get_linewidths()[0]))\n"
2689
            "plt.close(fig)\n"
2690
        )
NEW
2691
        return ast.parse(src).body
×
2692

2693
    def generate_failing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
2694
        test = self.get_empty_test()
×
NEW
2695
        test.body = self._assert(random.choice(_UNFILLED_MARKERS), self._lw())
×
NEW
2696
        return test, TestResult.FAILING
×
2697

2698
    def generate_passing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
2699
        test = self.get_empty_test()
×
NEW
2700
        test.body = self._assert(random.choice(_FILLED_MARKERS2), self._lw())
×
NEW
2701
        return test, TestResult.PASSING
×
2702

2703

2704
grammar_5: Grammar = clean_up(
1✔
2705
    dict(
2706
        {
2707
            "<start>": ["<marker> <float>"],
2708
            "<marker>": ["x", "+", "1", "2", "3", "4", "o", "s", "^", "D", "v", "p"],
2709
        },
2710
        **FLOAT,
2711
    )
2712
)
2713

2714
assert is_valid_grammar(grammar_5)
1✔
2715

2716

2717
# ======================================================================
2718
# bug_4: ``Axes.vlines``/``Axes.hlines`` hard-coded their default ``colors='k'``
2719
# instead of honouring ``rcParams['lines.color']``.  The fix changes the default
2720
# to ``None`` (which resolves to the rcParam).
2721
#
2722
# System-test format:  ``<func> <color> <pos> <lo> <hi>`` where ``<func>`` is
2723
# ``vlines``/``hlines`` and ``<color>`` is the ``lines.color`` rc value.  The
2724
# harness draws the line under that rc context (no explicit color) and prints
2725
# whether the drawn colour matches the rc colour.  The correct (fixed) answer is
2726
# always ``True``; a *non-black* rc colour triggers the fault (buggy draws
2727
# black), a black one never does.
2728
# ======================================================================
2729

2730
_NONBLACK_COLORS = [
1✔
2731
    "red", "green", "blue", "cyan", "magenta", "yellow", "orange",
2732
    "purple", "brown", "pink", "olive", "teal",
2733
]
2734
_BLACK_COLORS = ["black", "k", "#000000"]
1✔
2735

2736

2737
class Matplotlib4API(MatplotlibAPI):
1✔
2738
    def oracle(self, args: Any) -> Tuple[TestResult, str]:
1✔
NEW
2739
        if args is None:
×
NEW
2740
            return TestResult.UNDEFINED, "No process finished"
×
NEW
2741
        process: subprocess.CompletedProcess = args
×
NEW
2742
        try:
×
NEW
2743
            func = process.args[2]
×
NEW
2744
            if func not in ("vlines", "hlines"):
×
NEW
2745
                raise ValueError("bad func")
×
NEW
2746
            float(process.args[4])
×
NEW
2747
        except (IndexError, ValueError):
×
NEW
2748
            return TestResult.UNDEFINED, "Malformed test input"
×
NEW
2749
        expected = "True"  # fixed: line colour always equals the rc lines.color
×
NEW
2750
        out = process.stdout.decode("utf8").strip()
×
NEW
2751
        if process.returncode == 0 and out == expected:
×
NEW
2752
            return TestResult.PASSING, f"Expected {expected}"
×
NEW
2753
        return TestResult.FAILING, f"Expected {expected}, but was {out!r}"
×
2754

2755

2756
class Matplotlib4TestGenerator:
1✔
2757
    @staticmethod
1✔
2758
    def _pos_lo_hi() -> Tuple[float, float, float]:
1✔
NEW
2759
        pos = round(random.uniform(-5, 5), 2)
×
NEW
2760
        lo = round(random.uniform(-5, 0), 2)
×
NEW
2761
        hi = round(lo + random.uniform(1, 6), 2)
×
NEW
2762
        return pos, lo, hi
×
2763

2764
    @staticmethod
1✔
2765
    def _func() -> str:
1✔
NEW
2766
        return random.choice(["vlines", "hlines"])
×
2767

2768
    def make_failing(self) -> str:
1✔
NEW
2769
        pos, lo, hi = self._pos_lo_hi()
×
NEW
2770
        return f"{self._func()} {random.choice(_NONBLACK_COLORS)} {pos} {lo} {hi}"
×
2771

2772
    def make_passing(self) -> str:
1✔
NEW
2773
        pos, lo, hi = self._pos_lo_hi()
×
NEW
2774
        return f"{self._func()} {random.choice(_BLACK_COLORS)} {pos} {lo} {hi}"
×
2775

2776

2777
class Matplotlib4SystemtestGenerator(
1✔
2778
    SystemtestGenerator, Matplotlib4TestGenerator
2779
):
2780
    def generate_failing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
2781
        return self.make_failing(), TestResult.FAILING
×
2782

2783
    def generate_passing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
2784
        return self.make_passing(), TestResult.PASSING
×
2785

2786

2787
class Matplotlib4UnittestGenerator(
1✔
2788
    python.PythonGenerator, UnittestGenerator, Matplotlib4TestGenerator
2789
):
2790
    def get_imports(self) -> List[ast.stmt]:
1✔
NEW
2791
        return ast.parse(
×
2792
            "import matplotlib\n"
2793
            "matplotlib.use('Agg')\n"
2794
            "import matplotlib as mpl\n"
2795
            "import matplotlib.pyplot as plt\n"
2796
        ).body
2797

2798
    @staticmethod
1✔
2799
    def _assert(func: str, color: str, pos: float, lo: float, hi: float) -> List[ast.stmt]:
1✔
NEW
2800
        src = (
×
2801
            "fig, ax = plt.subplots()\n"
2802
            f"with mpl.rc_context({{'lines.color': {color!r}}}):\n"
2803
            f"    lines = ax.{func}({pos}, {lo}, {hi})\n"
2804
            f"    self.assertEqual(True, "
2805
            f"mpl.colors.same_color(lines.get_color(), {color!r}))\n"
2806
            "plt.close(fig)\n"
2807
        )
NEW
2808
        return ast.parse(src).body
×
2809

2810
    def generate_failing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
2811
        pos, lo, hi = self._pos_lo_hi()
×
NEW
2812
        test = self.get_empty_test()
×
NEW
2813
        test.body = self._assert(self._func(), random.choice(_NONBLACK_COLORS), pos, lo, hi)
×
NEW
2814
        return test, TestResult.FAILING
×
2815

2816
    def generate_passing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
2817
        pos, lo, hi = self._pos_lo_hi()
×
NEW
2818
        test = self.get_empty_test()
×
NEW
2819
        test.body = self._assert(self._func(), random.choice(_BLACK_COLORS), pos, lo, hi)
×
NEW
2820
        return test, TestResult.PASSING
×
2821

2822

2823
grammar_4: Grammar = clean_up(
1✔
2824
    dict(
2825
        {
2826
            "<start>": ["<func> <color> <float> <float> <float>"],
2827
            "<func>": ["vlines", "hlines"],
2828
            "<color>": _NONBLACK_COLORS + _BLACK_COLORS,
2829
        },
2830
        **FLOAT,
2831
    )
2832
)
2833

2834
assert is_valid_grammar(grammar_4)
1✔
2835

2836

2837
# ======================================================================
2838
# bug_21: ``Axes.boxplot`` built the box/whisker/cap/median line properties from
2839
# the general ``rcParams`` (including ``lines.marker``), so a non-default
2840
# ``lines.marker`` wrongly put markers on those lines.  The fix forces
2841
# ``marker=''`` for those four artist groups (fliers/means keep their markers).
2842
#
2843
# System-test format:  ``<mode> <group> <marker>``.  For ``mode=box`` the harness
2844
# sets ``rcParams['lines.marker']=marker`` and prints the marker of a
2845
# box/whisker/cap/median line (correct fixed value: ``''``).  For ``mode=flier``
2846
# it sets the flier/mean marker rcParam and prints that artist's marker (value:
2847
# ``marker``).  ``mode=box`` triggers the fault (buggy leaks the rc marker).
2848
# ======================================================================
2849

2850
_BOX_GROUPS = ["whiskers", "caps", "boxes", "medians"]
1✔
2851
_FLIER_GROUPS = ["fliers", "means"]
1✔
2852
_BOX_MARKERS = ["s", "o", "^", "x", "+", "D", "v", "p", "*", "h"]
1✔
2853

2854

2855
class Matplotlib21API(MatplotlibAPI):
1✔
2856
    def oracle(self, args: Any) -> Tuple[TestResult, str]:
1✔
NEW
2857
        if args is None:
×
NEW
2858
            return TestResult.UNDEFINED, "No process finished"
×
NEW
2859
        process: subprocess.CompletedProcess = args
×
NEW
2860
        try:
×
NEW
2861
            mode = process.args[2]
×
NEW
2862
            marker = process.args[4]
×
NEW
2863
            if mode not in ("box", "flier"):
×
NEW
2864
                raise ValueError("bad mode")
×
NEW
2865
        except (IndexError, ValueError):
×
NEW
2866
            return TestResult.UNDEFINED, "Malformed test input"
×
NEW
2867
        expected = "" if mode == "box" else marker
×
NEW
2868
        out = process.stdout.decode("utf8").strip()
×
NEW
2869
        if process.returncode == 0 and out == expected:
×
NEW
2870
            return TestResult.PASSING, f"Expected {expected!r}"
×
NEW
2871
        return TestResult.FAILING, f"Expected {expected!r}, but was {out!r}"
×
2872

2873

2874
class Matplotlib21TestGenerator:
1✔
2875
    def make_failing(self) -> str:
1✔
NEW
2876
        return (
×
2877
            f"box {random.choice(_BOX_GROUPS)} {random.choice(_BOX_MARKERS)}"
2878
        )
2879

2880
    def make_passing(self) -> str:
1✔
NEW
2881
        return (
×
2882
            f"flier {random.choice(_FLIER_GROUPS)} {random.choice(_BOX_MARKERS)}"
2883
        )
2884

2885

2886
class Matplotlib21SystemtestGenerator(
1✔
2887
    SystemtestGenerator, Matplotlib21TestGenerator
2888
):
2889
    def generate_failing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
2890
        return self.make_failing(), TestResult.FAILING
×
2891

2892
    def generate_passing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
2893
        return self.make_passing(), TestResult.PASSING
×
2894

2895

2896
class Matplotlib21UnittestGenerator(
1✔
2897
    python.PythonGenerator, UnittestGenerator, Matplotlib21TestGenerator
2898
):
2899
    def get_imports(self) -> List[ast.stmt]:
1✔
NEW
2900
        return ast.parse(
×
2901
            "import matplotlib\n"
2902
            "matplotlib.use('Agg')\n"
2903
            "import numpy as np\n"
2904
            "import matplotlib.pyplot as plt\n"
2905
        ).body
2906

2907
    @staticmethod
1✔
2908
    def _assert(mode: str, group: str, marker: str) -> List[ast.stmt]:
1✔
NEW
2909
        expected = "" if mode == "box" else marker
×
NEW
2910
        if mode == "box":
×
NEW
2911
            setrc = f"plt.rcParams['lines.marker'] = {marker!r}\n"
×
NEW
2912
        elif group == "fliers":
×
NEW
2913
            setrc = f"plt.rcParams['boxplot.flierprops.marker'] = {marker!r}\n"
×
2914
        else:
NEW
2915
            setrc = f"plt.rcParams['boxplot.meanprops.marker'] = {marker!r}\n"
×
NEW
2916
        src = (
×
2917
            "plt.rcdefaults()\n"
2918
            f"{setrc}"
2919
            "fig, ax = plt.subplots()\n"
2920
            "d = np.arange(100)\n"
2921
            "d[-1] = 150\n"
2922
            "bxp = ax.boxplot(d, showmeans=True)\n"
2923
            f"self.assertEqual({expected!r}, bxp[{group!r}][0].get_marker())\n"
2924
            "plt.close(fig)\n"
2925
            "plt.rcdefaults()\n"
2926
        )
NEW
2927
        return ast.parse(src).body
×
2928

2929
    def generate_failing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
2930
        test = self.get_empty_test()
×
NEW
2931
        test.body = self._assert(
×
2932
            "box", random.choice(_BOX_GROUPS), random.choice(_BOX_MARKERS)
2933
        )
NEW
2934
        return test, TestResult.FAILING
×
2935

2936
    def generate_passing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
2937
        test = self.get_empty_test()
×
NEW
2938
        test.body = self._assert(
×
2939
            "flier", random.choice(_FLIER_GROUPS), random.choice(_BOX_MARKERS)
2940
        )
NEW
2941
        return test, TestResult.PASSING
×
2942

2943

2944
grammar_21: Grammar = clean_up(
1✔
2945
    {
2946
        "<start>": ["<mode> <group> <marker>"],
2947
        "<mode>": ["box", "flier"],
2948
        "<group>": _BOX_GROUPS + _FLIER_GROUPS,
2949
        "<marker>": _BOX_MARKERS,
2950
    }
2951
)
2952

2953
assert is_valid_grammar(grammar_21)
1✔
2954

2955

2956
# ======================================================================
2957
# bug_27: ``Colorbar.set_label`` coerced its argument with ``str(label)``, so
2958
# ``set_label(None)`` produced the literal label ``"None"`` instead of clearing
2959
# it.  The fix stores ``label`` unchanged (``None`` -> empty label).
2960
#
2961
# System-test format:  ``<orientation> <initial> <label>`` where
2962
# ``<orientation>`` is ``v``/``h``, ``<initial>`` the constructor label and
2963
# ``<label>`` is either the literal ``NONE`` (``set_label(None)``) or a word.
2964
# The harness builds the colorbar, calls ``set_label`` and prints the long-axis
2965
# label.  The correct (fixed) label is ``''`` for ``NONE`` and the word
2966
# otherwise; ``NONE`` triggers the fault (buggy yields ``"None"``).
2967
# ======================================================================
2968

2969

2970
class Matplotlib27API(MatplotlibAPI):
1✔
2971
    def oracle(self, args: Any) -> Tuple[TestResult, str]:
1✔
NEW
2972
        if args is None:
×
NEW
2973
            return TestResult.UNDEFINED, "No process finished"
×
NEW
2974
        process: subprocess.CompletedProcess = args
×
NEW
2975
        try:
×
NEW
2976
            orientation = process.args[2]
×
NEW
2977
            label = process.args[4]
×
NEW
2978
            if orientation not in ("v", "h"):
×
NEW
2979
                raise ValueError("bad orientation")
×
NEW
2980
        except (IndexError, ValueError):
×
NEW
2981
            return TestResult.UNDEFINED, "Malformed test input"
×
NEW
2982
        expected = "" if label == "NONE" else label
×
NEW
2983
        out = process.stdout.decode("utf8").strip()
×
NEW
2984
        if process.returncode == 0 and out == expected:
×
NEW
2985
            return TestResult.PASSING, f"Expected {expected!r}"
×
NEW
2986
        return TestResult.FAILING, f"Expected {expected!r}, but was {out!r}"
×
2987

2988

2989
class Matplotlib27TestGenerator:
1✔
2990
    @staticmethod
1✔
2991
    def _word() -> str:
1✔
NEW
2992
        return "".join(
×
2993
            random.choice(string.ascii_lowercase)
2994
            for _ in range(random.randint(3, 8))
2995
        )
2996

2997
    @staticmethod
1✔
2998
    def _orient() -> str:
1✔
NEW
2999
        return random.choice(["v", "h"])
×
3000

3001
    def make_failing(self) -> str:
1✔
NEW
3002
        return f"{self._orient()} {self._word()} NONE"
×
3003

3004
    def make_passing(self) -> str:
1✔
NEW
3005
        return f"{self._orient()} {self._word()} {self._word()}"
×
3006

3007

3008
class Matplotlib27SystemtestGenerator(
1✔
3009
    SystemtestGenerator, Matplotlib27TestGenerator
3010
):
3011
    def generate_failing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
3012
        return self.make_failing(), TestResult.FAILING
×
3013

3014
    def generate_passing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
3015
        return self.make_passing(), TestResult.PASSING
×
3016

3017

3018
class Matplotlib27UnittestGenerator(
1✔
3019
    python.PythonGenerator, UnittestGenerator, Matplotlib27TestGenerator
3020
):
3021
    def get_imports(self) -> List[ast.stmt]:
1✔
NEW
3022
        return ast.parse(
×
3023
            "import matplotlib\n"
3024
            "matplotlib.use('Agg')\n"
3025
            "import matplotlib.pyplot as plt\n"
3026
        ).body
3027

3028
    @staticmethod
1✔
3029
    def _assert(orient: str, initial: str, label: str) -> List[ast.stmt]:
1✔
NEW
3030
        expected = "" if label == "NONE" else label
×
NEW
3031
        orientation = "vertical" if orient == "v" else "horizontal"
×
NEW
3032
        getlabel = "get_xlabel" if orient == "h" else "get_ylabel"
×
NEW
3033
        label_expr = "None" if label == "NONE" else repr(label)
×
NEW
3034
        src = (
×
3035
            "fig, ax = plt.subplots()\n"
3036
            "im = ax.imshow([[1, 2], [3, 4]])\n"
3037
            f"cbar = fig.colorbar(im, orientation={orientation!r}, "
3038
            f"label={initial!r})\n"
3039
            f"cbar.set_label({label_expr})\n"
3040
            f"self.assertEqual({expected!r}, cbar.ax.{getlabel}())\n"
3041
            "plt.close(fig)\n"
3042
        )
NEW
3043
        return ast.parse(src).body
×
3044

3045
    def generate_failing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
3046
        test = self.get_empty_test()
×
NEW
3047
        test.body = self._assert(self._orient(), self._word(), "NONE")
×
NEW
3048
        return test, TestResult.FAILING
×
3049

3050
    def generate_passing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
3051
        test = self.get_empty_test()
×
NEW
3052
        test.body = self._assert(self._orient(), self._word(), self._word())
×
NEW
3053
        return test, TestResult.PASSING
×
3054

3055

3056
grammar_27: Grammar = clean_up(
1✔
3057
    {
3058
        "<start>": ["<orientation> <word> <label>"],
3059
        "<orientation>": ["v", "h"],
3060
        "<label>": ["NONE", "<word>"],
3061
        "<word>": ["<letter>", "<letter><word>"],
3062
        "<letter>": srange(string.ascii_lowercase),
3063
    }
3064
)
3065

3066
assert is_valid_grammar(grammar_27)
1✔
3067

3068

3069
# ======================================================================
3070
# bug_15: ``matplotlib.colors.SymLogNorm`` had no ``base`` parameter (the log
3071
# base was hard-wired to ``np.e``).  The fix adds a ``base`` kwarg and divides
3072
# the logarithm by ``log(base)``.  A ``SymLogNorm(..., base=10)`` call therefore
3073
# raises ``TypeError`` on the buggy build and normalises correctly on the fixed
3074
# one.
3075
#
3076
# System-test format:  ``<mode> <linthresh> <linscale> <vmin> <vmax> <base>
3077
# <value>`` where ``<mode>`` is ``basekw`` (pass ``base=``) or ``nobase`` (omit
3078
# it, both builds use ``np.e``).  The harness prints ``norm(value)``.  The
3079
# correct (fixed) value is recomputed in pure Python; ``basekw`` triggers the
3080
# fault (buggy raises ``TypeError`` -> no output).
3081
# ======================================================================
3082

3083

3084
def _symlognorm_value(
1✔
3085
    linthresh: float,
3086
    linscale: float,
3087
    vmin: float,
3088
    vmax: float,
3089
    base: float,
3090
    value: float,
3091
) -> float:
NEW
3092
    import math
×
3093

NEW
3094
    adj = linscale / (1.0 - base ** -1)
×
NEW
3095
    log_base = math.log(base)
×
3096

NEW
3097
    def tr(a: float) -> float:
×
NEW
3098
        if abs(a) > linthresh:
×
NEW
3099
            s = 1.0 if a > 0 else (-1.0 if a < 0 else 0.0)
×
NEW
3100
            return (adj + math.log(abs(a) / linthresh) / log_base) * s * linthresh
×
NEW
3101
        return a * adj
×
3102

NEW
3103
    upper = tr(vmax)
×
NEW
3104
    lower = tr(vmin)
×
NEW
3105
    return (tr(value) - lower) / (upper - lower)
×
3106

3107

3108
def _parse_base(token: str) -> float:
1✔
NEW
3109
    import math
×
3110

NEW
3111
    if token == "e":
×
NEW
3112
        return math.e
×
NEW
3113
    return float(token)
×
3114

3115

3116
class Matplotlib15API(MatplotlibAPI):
1✔
3117
    def oracle(self, args: Any) -> Tuple[TestResult, str]:
1✔
NEW
3118
        if args is None:
×
NEW
3119
            return TestResult.UNDEFINED, "No process finished"
×
NEW
3120
        process: subprocess.CompletedProcess = args
×
NEW
3121
        try:
×
NEW
3122
            import math
×
3123

NEW
3124
            mode = process.args[2]
×
NEW
3125
            linthresh = float(process.args[3])
×
NEW
3126
            linscale = float(process.args[4])
×
NEW
3127
            vmin = float(process.args[5])
×
NEW
3128
            vmax = float(process.args[6])
×
NEW
3129
            base = math.e if mode == "nobase" else _parse_base(process.args[7])
×
NEW
3130
            value = float(process.args[8])
×
NEW
3131
            if mode not in ("basekw", "nobase"):
×
NEW
3132
                raise ValueError("bad mode")
×
NEW
3133
        except (IndexError, ValueError):
×
NEW
3134
            return TestResult.UNDEFINED, "Malformed test input"
×
NEW
3135
        expected = _symlognorm_value(linthresh, linscale, vmin, vmax, base, value)
×
NEW
3136
        out = process.stdout.decode("utf8").strip()
×
NEW
3137
        try:
×
NEW
3138
            got = float(out)
×
NEW
3139
        except ValueError:
×
NEW
3140
            return TestResult.FAILING, f"Expected {expected}, but was {out!r}"
×
NEW
3141
        if process.returncode == 0 and abs(got - expected) < 1e-4:
×
NEW
3142
            return TestResult.PASSING, f"Expected {expected}"
×
NEW
3143
        return TestResult.FAILING, f"Expected {expected}, but was {got}"
×
3144

3145

3146
class Matplotlib15TestGenerator:
1✔
3147
    @staticmethod
1✔
3148
    def _params() -> Tuple[float, float, float, float, float]:
1✔
NEW
3149
        linthresh = round(random.uniform(0.5, 3.0), 3)
×
NEW
3150
        linscale = round(random.uniform(0.5, 2.0), 3)
×
NEW
3151
        vmin = round(random.uniform(-30, -5), 3)
×
NEW
3152
        vmax = round(random.uniform(5, 30), 3)
×
NEW
3153
        value = round(random.uniform(vmin + 0.5, vmax - 0.5), 3)
×
NEW
3154
        return linthresh, linscale, vmin, vmax, value
×
3155

3156
    def make_failing(self) -> str:
1✔
NEW
3157
        lt, ls, vmin, vmax, val = self._params()
×
NEW
3158
        base = random.choice(["2", "e", "10"])
×
NEW
3159
        return f"basekw {lt} {ls} {vmin} {vmax} {base} {val}"
×
3160

3161
    def make_passing(self) -> str:
1✔
NEW
3162
        lt, ls, vmin, vmax, val = self._params()
×
NEW
3163
        return f"nobase {lt} {ls} {vmin} {vmax} e {val}"
×
3164

3165

3166
class Matplotlib15SystemtestGenerator(
1✔
3167
    SystemtestGenerator, Matplotlib15TestGenerator
3168
):
3169
    def generate_failing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
3170
        return self.make_failing(), TestResult.FAILING
×
3171

3172
    def generate_passing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
3173
        return self.make_passing(), TestResult.PASSING
×
3174

3175

3176
class Matplotlib15UnittestGenerator(
1✔
3177
    python.PythonGenerator, UnittestGenerator, Matplotlib15TestGenerator
3178
):
3179
    def get_imports(self) -> List[ast.stmt]:
1✔
NEW
3180
        return ast.parse(
×
3181
            "import math\n"
3182
            "import warnings\n"
3183
            "import matplotlib\n"
3184
            "matplotlib.use('Agg')\n"
3185
            "import matplotlib.colors as mcolors\n"
3186
        ).body
3187

3188
    @staticmethod
1✔
3189
    def _assert(mode: str, lt: float, ls: float, vmin: float, vmax: float,
1✔
3190
                base_token: str, value: float) -> List[ast.stmt]:
NEW
3191
        import math
×
3192

NEW
3193
        base = math.e if mode == "nobase" else _parse_base(base_token)
×
NEW
3194
        expected = _symlognorm_value(lt, ls, vmin, vmax, base, value)
×
NEW
3195
        if mode == "basekw":
×
NEW
3196
            base_expr = "math.e" if base_token == "e" else base_token
×
NEW
3197
            ctor = (
×
3198
                f"mcolors.SymLogNorm({lt}, {ls}, vmin={vmin}, vmax={vmax}, "
3199
                f"base={base_expr})"
3200
            )
3201
        else:
NEW
3202
            ctor = f"mcolors.SymLogNorm({lt}, {ls}, vmin={vmin}, vmax={vmax})"
×
NEW
3203
        src = (
×
3204
            "with warnings.catch_warnings():\n"
3205
            "    warnings.simplefilter('ignore')\n"
3206
            f"    norm = {ctor}\n"
3207
            f"    self.assertAlmostEqual({expected!r}, float(norm({value})), places=4)\n"
3208
        )
NEW
3209
        return ast.parse(src).body
×
3210

3211
    def generate_failing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
3212
        lt, ls, vmin, vmax, val = self._params()
×
NEW
3213
        base = random.choice(["2", "e", "10"])
×
NEW
3214
        test = self.get_empty_test()
×
NEW
3215
        test.body = self._assert("basekw", lt, ls, vmin, vmax, base, val)
×
NEW
3216
        return test, TestResult.FAILING
×
3217

3218
    def generate_passing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
3219
        lt, ls, vmin, vmax, val = self._params()
×
NEW
3220
        test = self.get_empty_test()
×
NEW
3221
        test.body = self._assert("nobase", lt, ls, vmin, vmax, "e", val)
×
NEW
3222
        return test, TestResult.PASSING
×
3223

3224

3225
grammar_15: Grammar = clean_up(
1✔
3226
    dict(
3227
        {
3228
            "<start>": ["<mode> <float> <float> <float> <float> <base> <float>"],
3229
            "<mode>": ["basekw", "nobase"],
3230
            "<base>": ["2", "e", "10"],
3231
        },
3232
        **FLOAT,
3233
    )
3234
)
3235

3236
assert is_valid_grammar(grammar_15)
1✔
3237

3238

3239
# ======================================================================
3240
# bug_29: ``Axis.set_inverted`` used ``set_view_interval`` directly, which does
3241
# not propagate to *shared* axes.  The fix reimplements ``set_inverted`` in the
3242
# ``XAxis``/``YAxis`` subclasses via ``set_xlim``/``set_ylim`` so shared
3243
# siblings get inverted too.
3244
#
3245
# System-test format:  ``<axis> <inverted> <lo> <hi>`` where ``<axis>`` is
3246
# ``x``/``y``, ``<inverted>`` is ``true``/``false`` and ``(lo, hi)`` seed the
3247
# primary axes' data.  The harness makes two axes sharing that axis, inverts the
3248
# primary and prints whether the *sibling* is inverted.  The correct (fixed)
3249
# answer is the ``inverted`` flag; inverting (``true``) triggers the fault
3250
# (buggy leaves the sibling un-inverted).
3251
# ======================================================================
3252

3253

3254
class Matplotlib29API(MatplotlibAPI):
1✔
3255
    def oracle(self, args: Any) -> Tuple[TestResult, str]:
1✔
NEW
3256
        if args is None:
×
NEW
3257
            return TestResult.UNDEFINED, "No process finished"
×
NEW
3258
        process: subprocess.CompletedProcess = args
×
NEW
3259
        try:
×
NEW
3260
            axis = process.args[2]
×
NEW
3261
            inverted = process.args[3]
×
NEW
3262
            if axis not in ("x", "y") or inverted not in ("true", "false"):
×
NEW
3263
                raise ValueError("bad args")
×
NEW
3264
        except (IndexError, ValueError):
×
NEW
3265
            return TestResult.UNDEFINED, "Malformed test input"
×
NEW
3266
        expected = str(inverted == "true")
×
NEW
3267
        out = process.stdout.decode("utf8").strip()
×
NEW
3268
        if process.returncode == 0 and out == expected:
×
NEW
3269
            return TestResult.PASSING, f"Expected {expected}"
×
NEW
3270
        return TestResult.FAILING, f"Expected {expected}, but was {out!r}"
×
3271

3272

3273
class Matplotlib29TestGenerator:
1✔
3274
    @staticmethod
1✔
3275
    def _lo_hi() -> Tuple[float, float]:
1✔
NEW
3276
        lo = round(random.uniform(-20, 10), 2)
×
NEW
3277
        hi = round(lo + random.uniform(1, 20), 2)
×
NEW
3278
        return lo, hi
×
3279

3280
    @staticmethod
1✔
3281
    def _axis() -> str:
1✔
NEW
3282
        return random.choice(["x", "y"])
×
3283

3284
    def make_failing(self) -> str:
1✔
NEW
3285
        lo, hi = self._lo_hi()
×
NEW
3286
        return f"{self._axis()} true {lo} {hi}"
×
3287

3288
    def make_passing(self) -> str:
1✔
NEW
3289
        lo, hi = self._lo_hi()
×
NEW
3290
        return f"{self._axis()} false {lo} {hi}"
×
3291

3292

3293
class Matplotlib29SystemtestGenerator(
1✔
3294
    SystemtestGenerator, Matplotlib29TestGenerator
3295
):
3296
    def generate_failing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
3297
        return self.make_failing(), TestResult.FAILING
×
3298

3299
    def generate_passing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
3300
        return self.make_passing(), TestResult.PASSING
×
3301

3302

3303
class Matplotlib29UnittestGenerator(
1✔
3304
    python.PythonGenerator, UnittestGenerator, Matplotlib29TestGenerator
3305
):
3306
    def get_imports(self) -> List[ast.stmt]:
1✔
NEW
3307
        return ast.parse(
×
3308
            "import matplotlib\n"
3309
            "matplotlib.use('Agg')\n"
3310
            "import matplotlib.pyplot as plt\n"
3311
        ).body
3312

3313
    @staticmethod
1✔
3314
    def _assert(axis: str, inverted: bool, lo: float, hi: float) -> List[ast.stmt]:
1✔
NEW
3315
        share = "sharey" if axis == "y" else "sharex"
×
NEW
3316
        src = (
×
3317
            "fig = plt.figure()\n"
3318
            "ax0 = plt.subplot(211)\n"
3319
            f"ax1 = plt.subplot(212, {share}=ax0)\n"
3320
            f"ax0.plot([{lo}, {hi}], [{lo}, {hi}])\n"
3321
            f"ax0.{axis}axis.set_inverted({inverted!r})\n"
3322
            f"self.assertEqual({inverted!r}, ax1.{axis}axis_inverted())\n"
3323
            "plt.close(fig)\n"
3324
        )
NEW
3325
        return ast.parse(src).body
×
3326

3327
    def generate_failing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
3328
        lo, hi = self._lo_hi()
×
NEW
3329
        test = self.get_empty_test()
×
NEW
3330
        test.body = self._assert(self._axis(), True, lo, hi)
×
NEW
3331
        return test, TestResult.FAILING
×
3332

3333
    def generate_passing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
3334
        lo, hi = self._lo_hi()
×
NEW
3335
        test = self.get_empty_test()
×
NEW
3336
        test.body = self._assert(self._axis(), False, lo, hi)
×
NEW
3337
        return test, TestResult.PASSING
×
3338

3339

3340
grammar_29: Grammar = clean_up(
1✔
3341
    dict(
3342
        {
3343
            "<start>": ["<axis> <inverted> <float> <float>"],
3344
            "<axis>": ["x", "y"],
3345
            "<inverted>": ["true", "false"],
3346
        },
3347
        **FLOAT,
3348
    )
3349
)
3350

3351
assert is_valid_grammar(grammar_29)
1✔
3352

3353

3354
# ======================================================================
3355
# bug_28: on a *log*-scaled x-axis, ``set_xlim`` with a non-positive endpoint
3356
# tried to fall back to ``old_left``/``old_right`` which were only assigned in a
3357
# different branch, raising ``UnboundLocalError``.  The fix fetches the old
3358
# limits inside the log branch before using them.  (Only ``set_xlim`` is fixed
3359
# by this commit, so the subject uses the x-axis exclusively.)
3360
#
3361
# System-test format:  ``<axis> <left> <right>`` (axis is always ``x``).  The
3362
# harness log-scales the axis, calls ``set_xlim(left, right)`` and prints ``OK``
3363
# on success.  A non-positive endpoint triggers the fault (buggy crashes -> no
3364
# output); two positive endpoints never do.  The correct (fixed) result is
3365
# always ``OK`` (the invalid limit is warned about and ignored).
3366
# ======================================================================
3367

3368

3369
class Matplotlib28API(MatplotlibAPI):
1✔
3370
    def oracle(self, args: Any) -> Tuple[TestResult, str]:
1✔
NEW
3371
        if args is None:
×
NEW
3372
            return TestResult.UNDEFINED, "No process finished"
×
NEW
3373
        process: subprocess.CompletedProcess = args
×
NEW
3374
        try:
×
NEW
3375
            axis = process.args[2]
×
NEW
3376
            float(process.args[3])
×
NEW
3377
            float(process.args[4])
×
NEW
3378
            if axis != "x":
×
NEW
3379
                raise ValueError("bad axis")
×
NEW
3380
        except (IndexError, ValueError):
×
NEW
3381
            return TestResult.UNDEFINED, "Malformed test input"
×
NEW
3382
        expected = "OK"
×
NEW
3383
        out = process.stdout.decode("utf8").strip()
×
NEW
3384
        if process.returncode == 0 and out == expected:
×
NEW
3385
            return TestResult.PASSING, f"Expected {expected}"
×
NEW
3386
        return TestResult.FAILING, f"Expected {expected}, but was {out!r}"
×
3387

3388

3389
class Matplotlib28TestGenerator:
1✔
3390
    @staticmethod
1✔
3391
    def _axis() -> str:
1✔
NEW
3392
        return "x"
×
3393

3394
    def make_failing(self) -> str:
1✔
3395
        # A non-positive endpoint on a log axis.
NEW
3396
        left = round(random.uniform(-10, -0.1), 2)
×
NEW
3397
        right = round(random.uniform(1, 100), 2)
×
NEW
3398
        return f"{self._axis()} {left} {right}"
×
3399

3400
    def make_passing(self) -> str:
1✔
NEW
3401
        left = round(random.uniform(0.1, 5), 2)
×
NEW
3402
        right = round(left + random.uniform(1, 50), 2)
×
NEW
3403
        return f"{self._axis()} {left} {right}"
×
3404

3405

3406
class Matplotlib28SystemtestGenerator(
1✔
3407
    SystemtestGenerator, Matplotlib28TestGenerator
3408
):
3409
    def generate_failing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
3410
        return self.make_failing(), TestResult.FAILING
×
3411

3412
    def generate_passing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
3413
        return self.make_passing(), TestResult.PASSING
×
3414

3415

3416
class Matplotlib28UnittestGenerator(
1✔
3417
    python.PythonGenerator, UnittestGenerator, Matplotlib28TestGenerator
3418
):
3419
    def get_imports(self) -> List[ast.stmt]:
1✔
NEW
3420
        return ast.parse(
×
3421
            "import warnings\n"
3422
            "import matplotlib\n"
3423
            "matplotlib.use('Agg')\n"
3424
            "import matplotlib.pyplot as plt\n"
3425
        ).body
3426

3427
    @staticmethod
1✔
3428
    def _assert(axis: str, left: float, right: float) -> List[ast.stmt]:
1✔
NEW
3429
        src = (
×
3430
            "fig, ax = plt.subplots()\n"
3431
            f"ax.set_{axis}scale('log')\n"
3432
            "ok = 'FAIL'\n"
3433
            "with warnings.catch_warnings():\n"
3434
            "    warnings.simplefilter('ignore')\n"
3435
            f"    ax.set_{axis}lim({left}, {right})\n"
3436
            "    ok = 'OK'\n"
3437
            "self.assertEqual('OK', ok)\n"
3438
            "plt.close(fig)\n"
3439
        )
NEW
3440
        return ast.parse(src).body
×
3441

3442
    def generate_failing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
3443
        left = round(random.uniform(-10, -0.1), 2)
×
NEW
3444
        right = round(random.uniform(1, 100), 2)
×
NEW
3445
        test = self.get_empty_test()
×
NEW
3446
        test.body = self._assert(self._axis(), left, right)
×
NEW
3447
        return test, TestResult.FAILING
×
3448

3449
    def generate_passing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
3450
        left = round(random.uniform(0.1, 5), 2)
×
NEW
3451
        right = round(left + random.uniform(1, 50), 2)
×
NEW
3452
        test = self.get_empty_test()
×
NEW
3453
        test.body = self._assert(self._axis(), left, right)
×
NEW
3454
        return test, TestResult.PASSING
×
3455

3456

3457
grammar_28: Grammar = clean_up(
1✔
3458
    dict(
3459
        {
3460
            "<start>": ["<axis> <float> <float>"],
3461
            "<axis>": ["x"],
3462
        },
3463
        **FLOAT,
3464
    )
3465
)
3466

3467
assert is_valid_grammar(grammar_28)
1✔
3468

3469

3470
# ======================================================================
3471
# bug_8: ``set_xlim``/``set_ylim`` set the view interval but did not clear the
3472
# ``_stale_viewlim`` flag, so the next ``draw`` re-autoscaled and *reverted* the
3473
# freshly-set limits (unless autoscaling had been turned off).  The fix clears
3474
# the stale-viewlim flag for the axes and its shared siblings.
3475
#
3476
# System-test format:  ``<axis> <auto> <lo> <hi>`` where ``<axis>`` is ``x``/``y``
3477
# and ``<auto>`` is ``true``/``false``/``none``.  The harness scatters data whose
3478
# range differs from ``(lo, hi)``, sets the limit with that ``auto`` value,
3479
# draws, and prints the resulting limit.  The correct (fixed) limit is always
3480
# ``(lo, hi)``; ``auto`` ``true``/``none`` triggers the fault (buggy re-autoscales
3481
# back to the data range).
3482
# ======================================================================
3483

3484
_AUTO_MAP = {"true": True, "false": False, "none": None}
1✔
3485

3486

3487
class Matplotlib8API(MatplotlibAPI):
1✔
3488
    def oracle(self, args: Any) -> Tuple[TestResult, str]:
1✔
NEW
3489
        if args is None:
×
NEW
3490
            return TestResult.UNDEFINED, "No process finished"
×
NEW
3491
        process: subprocess.CompletedProcess = args
×
NEW
3492
        try:
×
NEW
3493
            axis = process.args[2]
×
NEW
3494
            auto = process.args[3]
×
NEW
3495
            lo = round(float(process.args[4]), 3)
×
NEW
3496
            hi = round(float(process.args[5]), 3)
×
NEW
3497
            if axis not in ("x", "y") or auto not in _AUTO_MAP:
×
NEW
3498
                raise ValueError("bad args")
×
NEW
3499
        except (IndexError, ValueError):
×
NEW
3500
            return TestResult.UNDEFINED, "Malformed test input"
×
NEW
3501
        expected = str([lo, hi])
×
NEW
3502
        out = process.stdout.decode("utf8").strip()
×
NEW
3503
        if process.returncode == 0 and out == expected:
×
NEW
3504
            return TestResult.PASSING, f"Expected {expected}"
×
NEW
3505
        return TestResult.FAILING, f"Expected {expected}, but was {out!r}"
×
3506

3507

3508
class Matplotlib8TestGenerator:
1✔
3509
    @staticmethod
1✔
3510
    def _axis() -> str:
1✔
NEW
3511
        return random.choice(["x", "y"])
×
3512

3513
    @staticmethod
1✔
3514
    def _lo_hi() -> Tuple[float, float]:
1✔
3515
        # Clearly wider than the data range (~+-0.11) so a buggy re-autoscale
3516
        # produces a visibly different limit.
NEW
3517
        lo = round(random.uniform(-2.0, -0.3), 3)
×
NEW
3518
        hi = round(random.uniform(0.3, 2.0), 3)
×
NEW
3519
        return lo, hi
×
3520

3521
    def make_failing(self) -> str:
1✔
NEW
3522
        lo, hi = self._lo_hi()
×
NEW
3523
        return f"{self._axis()} {random.choice(['true', 'none'])} {lo} {hi}"
×
3524

3525
    def make_passing(self) -> str:
1✔
NEW
3526
        lo, hi = self._lo_hi()
×
NEW
3527
        return f"{self._axis()} false {lo} {hi}"
×
3528

3529

3530
class Matplotlib8SystemtestGenerator(
1✔
3531
    SystemtestGenerator, Matplotlib8TestGenerator
3532
):
3533
    def generate_failing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
3534
        return self.make_failing(), TestResult.FAILING
×
3535

3536
    def generate_passing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
3537
        return self.make_passing(), TestResult.PASSING
×
3538

3539

3540
class Matplotlib8UnittestGenerator(
1✔
3541
    python.PythonGenerator, UnittestGenerator, Matplotlib8TestGenerator
3542
):
3543
    def get_imports(self) -> List[ast.stmt]:
1✔
NEW
3544
        return ast.parse(
×
3545
            "import matplotlib\n"
3546
            "matplotlib.use('Agg')\n"
3547
            "import numpy as np\n"
3548
            "import matplotlib.pyplot as plt\n"
3549
        ).body
3550

3551
    @staticmethod
1✔
3552
    def _assert(axis: str, auto: str, lo: float, hi: float) -> List[ast.stmt]:
1✔
NEW
3553
        expected = [round(lo, 3), round(hi, 3)]
×
NEW
3554
        auto_val = _AUTO_MAP[auto]
×
NEW
3555
        if axis == "y":
×
NEW
3556
            scatter = "ax.scatter(np.arange(100), np.linspace(-.1, .1, 100))"
×
3557
        else:
NEW
3558
            scatter = "ax.scatter(np.linspace(-.1, .1, 100), np.arange(100))"
×
NEW
3559
        src = (
×
3560
            "fig, ax = plt.subplots()\n"
3561
            f"{scatter}\n"
3562
            f"ax.set_{axis}lim(({lo}, {hi}), auto={auto_val!r})\n"
3563
            "fig.canvas.draw()\n"
3564
            f"self.assertEqual({expected!r}, "
3565
            f"[round(float(v), 3) for v in ax.get_{axis}lim()])\n"
3566
            "plt.close(fig)\n"
3567
        )
NEW
3568
        return ast.parse(src).body
×
3569

3570
    def generate_failing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
3571
        lo, hi = self._lo_hi()
×
NEW
3572
        test = self.get_empty_test()
×
NEW
3573
        test.body = self._assert(self._axis(), random.choice(["true", "none"]), lo, hi)
×
NEW
3574
        return test, TestResult.FAILING
×
3575

3576
    def generate_passing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
3577
        lo, hi = self._lo_hi()
×
NEW
3578
        test = self.get_empty_test()
×
NEW
3579
        test.body = self._assert(self._axis(), "false", lo, hi)
×
NEW
3580
        return test, TestResult.PASSING
×
3581

3582

3583
grammar_8: Grammar = clean_up(
1✔
3584
    dict(
3585
        {
3586
            "<start>": ["<axis> <auto> <float> <float>"],
3587
            "<axis>": ["x", "y"],
3588
            "<auto>": ["true", "false", "none"],
3589
        },
3590
        **FLOAT,
3591
    )
3592
)
3593

3594
assert is_valid_grammar(grammar_8)
1✔
3595

3596

3597
# ======================================================================
3598
# bug_18/bug_19: a polar axes with *no data* autoscaled its radial axis from the
3599
# initial ``(-inf, inf)`` limits and expanded them to a tiny ``(-0.055, 0.055)``
3600
# instead of the documented ``(0, 1)``.  The fix adds ``RadialLocator.nonsingular``
3601
# which returns ``(0, 1)`` for the initial limits.
3602
#
3603
# System-test format:  ``<trigger> <w> <h>`` where ``<trigger>`` selects how the
3604
# empty polar axes is exercised (``autoscale``/``relim``/``polar`` force the
3605
# faulty autoscale; ``none``/``draw`` do not) and ``(w, h)`` is the figure size.
3606
# The harness prints the resulting ``[rmin, rmax]``.  The correct (fixed) value
3607
# is ``[0.0, 1.0]`` for the ``none``/``draw`` triggers and ``[0.0, 1.05]`` for the
3608
# autoscaling triggers (the 5% radial margin on the ``(0, 1)`` default); the
3609
# buggy build instead collapses the autoscaling triggers to ``[-0.055, 0.055]``.
3610
# ======================================================================
3611

3612
_POLAR_FAULT_TRIGGERS = ("autoscale", "relim", "polar")
1✔
3613

3614

3615
def _polar_expected(trigger: str) -> List[float]:
1✔
NEW
3616
    return [0.0, 1.05] if trigger in _POLAR_FAULT_TRIGGERS else [0.0, 1.0]
×
3617

3618

3619
class Matplotlib18API(MatplotlibAPI):
1✔
3620
    def oracle(self, args: Any) -> Tuple[TestResult, str]:
1✔
NEW
3621
        if args is None:
×
NEW
3622
            return TestResult.UNDEFINED, "No process finished"
×
NEW
3623
        process: subprocess.CompletedProcess = args
×
NEW
3624
        try:
×
NEW
3625
            trigger = process.args[2]
×
NEW
3626
            float(process.args[3])
×
NEW
3627
            float(process.args[4])
×
NEW
3628
            if trigger not in ("autoscale", "relim", "polar", "none", "draw"):
×
NEW
3629
                raise ValueError("bad trigger")
×
NEW
3630
        except (IndexError, ValueError):
×
NEW
3631
            return TestResult.UNDEFINED, "Malformed test input"
×
NEW
3632
        expected = str(_polar_expected(trigger))
×
NEW
3633
        out = process.stdout.decode("utf8").strip()
×
NEW
3634
        if process.returncode == 0 and out == expected:
×
NEW
3635
            return TestResult.PASSING, f"Expected {expected}"
×
NEW
3636
        return TestResult.FAILING, f"Expected {expected}, but was {out!r}"
×
3637

3638

3639
class Matplotlib18TestGenerator:
1✔
3640
    @staticmethod
1✔
3641
    def _wh() -> Tuple[float, float]:
1✔
NEW
3642
        return round(random.uniform(3.0, 8.0), 2), round(random.uniform(3.0, 8.0), 2)
×
3643

3644
    def make_failing(self) -> str:
1✔
NEW
3645
        w, h = self._wh()
×
NEW
3646
        return f"{random.choice(['autoscale', 'relim', 'polar'])} {w} {h}"
×
3647

3648
    def make_passing(self) -> str:
1✔
NEW
3649
        w, h = self._wh()
×
NEW
3650
        return f"{random.choice(['none', 'draw'])} {w} {h}"
×
3651

3652

3653
class Matplotlib18SystemtestGenerator(
1✔
3654
    SystemtestGenerator, Matplotlib18TestGenerator
3655
):
3656
    def generate_failing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
3657
        return self.make_failing(), TestResult.FAILING
×
3658

3659
    def generate_passing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
3660
        return self.make_passing(), TestResult.PASSING
×
3661

3662

3663
class Matplotlib18UnittestGenerator(
1✔
3664
    python.PythonGenerator, UnittestGenerator, Matplotlib18TestGenerator
3665
):
3666
    def get_imports(self) -> List[ast.stmt]:
1✔
NEW
3667
        return ast.parse(
×
3668
            "import matplotlib\n"
3669
            "matplotlib.use('Agg')\n"
3670
            "import matplotlib.pyplot as plt\n"
3671
        ).body
3672

3673
    @staticmethod
1✔
3674
    def _assert(trigger: str, w: float, h: float) -> List[ast.stmt]:
1✔
NEW
3675
        if trigger == "polar":
×
NEW
3676
            setup = (
×
3677
                f"plt.figure(figsize=({w}, {h}))\n"
3678
                "plt.polar()\n"
3679
                "ax = plt.gca()\n"
3680
            )
3681
        else:
NEW
3682
            setup = (
×
3683
                f"fig = plt.figure(figsize=({w}, {h}))\n"
3684
                "ax = fig.add_subplot(projection='polar')\n"
3685
            )
NEW
3686
            if trigger == "draw":
×
NEW
3687
                setup += "fig.canvas.draw()\n"
×
NEW
3688
            elif trigger == "autoscale":
×
NEW
3689
                setup += "ax.autoscale()\nfig.canvas.draw()\n"
×
NEW
3690
            elif trigger == "relim":
×
NEW
3691
                setup += "ax.relim()\nax.autoscale_view()\nfig.canvas.draw()\n"
×
NEW
3692
        expected = _polar_expected(trigger)
×
NEW
3693
        src = (
×
3694
            f"{setup}"
3695
            f"self.assertEqual({expected!r}, "
3696
            "[round(float(ax.get_rmin()), 4), round(float(ax.get_rmax()), 4)])\n"
3697
            "plt.close('all')\n"
3698
        )
NEW
3699
        return ast.parse(src).body
×
3700

3701
    def generate_failing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
3702
        w, h = self._wh()
×
NEW
3703
        test = self.get_empty_test()
×
NEW
3704
        test.body = self._assert(random.choice(["autoscale", "relim", "polar"]), w, h)
×
NEW
3705
        return test, TestResult.FAILING
×
3706

3707
    def generate_passing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
3708
        w, h = self._wh()
×
NEW
3709
        test = self.get_empty_test()
×
NEW
3710
        test.body = self._assert(random.choice(["none", "draw"]), w, h)
×
NEW
3711
        return test, TestResult.PASSING
×
3712

3713

3714
grammar_18: Grammar = clean_up(
1✔
3715
    dict(
3716
        {
3717
            "<start>": ["<trigger> <float> <float>"],
3718
            "<trigger>": ["autoscale", "relim", "polar", "none", "draw"],
3719
        },
3720
        **FLOAT,
3721
    )
3722
)
3723

3724
assert is_valid_grammar(grammar_18)
1✔
3725

3726

3727
# bug_19 is the identical polar no-data ``RadialLocator.nonsingular`` fault as
3728
# bug_18 (a different buggy/fixed commit pair), so it reuses everything.
3729
Matplotlib19API = Matplotlib18API
1✔
3730
Matplotlib19SystemtestGenerator = Matplotlib18SystemtestGenerator
1✔
3731
Matplotlib19UnittestGenerator = Matplotlib18UnittestGenerator
1✔
3732
grammar_19 = grammar_18
1✔
3733

3734

3735
# ======================================================================
3736
# bug_12: ``Axes.vlines``/``hlines`` used ``cbook.delete_masked_points`` which
3737
# *removed* masked/NaN entries, shifting every following line's colour by one
3738
# (and shrinking the collection).  The fix uses ``_combine_masks`` to build a
3739
# masked vertex array, so masked entries become *empty* segments that keep the
3740
# per-line colour alignment.
3741
#
3742
# System-test format:  ``<func> <data>`` where ``<func>`` is ``vlines``/``hlines``
3743
# and ``<data>`` is a comma-separated list of numbers with optional ``nan``
3744
# entries.  The harness draws the lines and prints the number of segments.  The
3745
# correct (fixed) count equals the number of data points (masked -> empty
3746
# segment); a ``nan`` entry triggers the fault (buggy drops the segment).
3747
# ======================================================================
3748

3749

3750
class Matplotlib12API(MatplotlibAPI):
1✔
3751
    def oracle(self, args: Any) -> Tuple[TestResult, str]:
1✔
NEW
3752
        if args is None:
×
NEW
3753
            return TestResult.UNDEFINED, "No process finished"
×
NEW
3754
        process: subprocess.CompletedProcess = args
×
NEW
3755
        try:
×
NEW
3756
            func = process.args[2]
×
NEW
3757
            tokens = process.args[3].split(",")
×
NEW
3758
            if func not in ("vlines", "hlines") or not tokens:
×
NEW
3759
                raise ValueError("bad args")
×
NEW
3760
            for t in tokens:
×
NEW
3761
                if t != "nan":
×
NEW
3762
                    float(t)
×
NEW
3763
        except (IndexError, ValueError):
×
NEW
3764
            return TestResult.UNDEFINED, "Malformed test input"
×
NEW
3765
        expected = str(len(tokens))
×
NEW
3766
        out = process.stdout.decode("utf8").strip()
×
NEW
3767
        if process.returncode == 0 and out == expected:
×
NEW
3768
            return TestResult.PASSING, f"Expected {expected}"
×
NEW
3769
        return TestResult.FAILING, f"Expected {expected}, but was {out!r}"
×
3770

3771

3772
class Matplotlib12TestGenerator:
1✔
3773
    @staticmethod
1✔
3774
    def _func() -> str:
1✔
NEW
3775
        return random.choice(["vlines", "hlines"])
×
3776

3777
    @staticmethod
1✔
3778
    def _values(n: int) -> List[float]:
1✔
NEW
3779
        return [round(random.uniform(-10, 10), 2) for _ in range(n)]
×
3780

3781
    def make_failing(self) -> str:
1✔
NEW
3782
        n = random.randint(4, 7)
×
NEW
3783
        vals = [str(v) for v in self._values(n)]
×
3784
        # Replace 1-2 interior entries with nan (keep >=2 real points).
NEW
3785
        k = random.randint(1, 2)
×
NEW
3786
        idxs = random.sample(range(n), k)
×
NEW
3787
        for i in idxs:
×
NEW
3788
            vals[i] = "nan"
×
NEW
3789
        return f"{self._func()} {','.join(vals)}"
×
3790

3791
    def make_passing(self) -> str:
1✔
NEW
3792
        n = random.randint(2, 7)
×
NEW
3793
        vals = [str(v) for v in self._values(n)]
×
NEW
3794
        return f"{self._func()} {','.join(vals)}"
×
3795

3796

3797
class Matplotlib12SystemtestGenerator(
1✔
3798
    SystemtestGenerator, Matplotlib12TestGenerator
3799
):
3800
    def generate_failing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
3801
        return self.make_failing(), TestResult.FAILING
×
3802

3803
    def generate_passing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
3804
        return self.make_passing(), TestResult.PASSING
×
3805

3806

3807
class Matplotlib12UnittestGenerator(
1✔
3808
    python.PythonGenerator, UnittestGenerator, Matplotlib12TestGenerator
3809
):
3810
    def get_imports(self) -> List[ast.stmt]:
1✔
NEW
3811
        return ast.parse(
×
3812
            "import matplotlib\n"
3813
            "matplotlib.use('Agg')\n"
3814
            "import matplotlib.pyplot as plt\n"
3815
        ).body
3816

3817
    @staticmethod
1✔
3818
    def _assert(func: str, tokens: List[str]) -> List[ast.stmt]:
1✔
NEW
3819
        expected = len(tokens)
×
NEW
3820
        data = "[" + ", ".join(
×
3821
            "float('nan')" if t == "nan" else t for t in tokens
3822
        ) + "]"
NEW
3823
        src = (
×
3824
            "fig, ax = plt.subplots()\n"
3825
            f"coll = ax.{func}({data}, 0, 1)\n"
3826
            f"self.assertEqual({expected!r}, len(coll.get_segments()))\n"
3827
            "plt.close(fig)\n"
3828
        )
NEW
3829
        return ast.parse(src).body
×
3830

3831
    def generate_failing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
3832
        n = random.randint(4, 7)
×
NEW
3833
        vals = [str(v) for v in self._values(n)]
×
NEW
3834
        k = random.randint(1, 2)
×
NEW
3835
        for i in random.sample(range(n), k):
×
NEW
3836
            vals[i] = "nan"
×
NEW
3837
        test = self.get_empty_test()
×
NEW
3838
        test.body = self._assert(self._func(), vals)
×
NEW
3839
        return test, TestResult.FAILING
×
3840

3841
    def generate_passing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
3842
        n = random.randint(2, 7)
×
NEW
3843
        vals = [str(v) for v in self._values(n)]
×
NEW
3844
        test = self.get_empty_test()
×
NEW
3845
        test.body = self._assert(self._func(), vals)
×
NEW
3846
        return test, TestResult.PASSING
×
3847

3848

3849
grammar_12: Grammar = clean_up(
1✔
3850
    dict(
3851
        {
3852
            "<start>": ["<func> <data>"],
3853
            "<func>": ["vlines", "hlines"],
3854
            "<data>": ["<item>", "<item>,<data>"],
3855
            "<item>": ["<float>", "nan"],
3856
        },
3857
        **FLOAT,
3858
    )
3859
)
3860

3861
assert is_valid_grammar(grammar_12)
1✔
3862

3863

3864
# ======================================================================
3865
# bug_6: ``Axes.scatter`` misclassified a single-row RGB(A) ``c`` (shape
3866
# ``(1, 3)`` / ``(1, 4)``) as *mapped values* whenever its size happened to equal
3867
# the number of points, instead of broadcasting it as one colour.  The fix
3868
# special-cases the ``(1, 3)``/``(1, 4)`` shape as a single colour.
3869
#
3870
# System-test format:  ``<n> <color>`` where ``<n>`` is the number of points and
3871
# ``<color>`` is a comma-separated RGB or RGBA colour.  The harness scatters with
3872
# ``c=[color]`` and prints whether the collection has *no* mapped array (i.e. the
3873
# colour was taken as a single colour).  The correct (fixed) answer is always
3874
# ``True``; a colour whose component count equals ``n`` triggers the fault (buggy
3875
# maps it through the colormap instead).
3876
# ======================================================================
3877

3878

3879
class Matplotlib6API(MatplotlibAPI):
1✔
3880
    def oracle(self, args: Any) -> Tuple[TestResult, str]:
1✔
NEW
3881
        if args is None:
×
NEW
3882
            return TestResult.UNDEFINED, "No process finished"
×
NEW
3883
        process: subprocess.CompletedProcess = args
×
NEW
3884
        try:
×
NEW
3885
            int(process.args[2])
×
NEW
3886
            comps = process.args[3].split(",")
×
NEW
3887
            if len(comps) not in (3, 4):
×
NEW
3888
                raise ValueError("bad color")
×
NEW
3889
            for x in comps:
×
NEW
3890
                float(x)
×
NEW
3891
        except (IndexError, ValueError):
×
NEW
3892
            return TestResult.UNDEFINED, "Malformed test input"
×
NEW
3893
        expected = "True"  # fixed always treats a single RGB(A) row as one colour
×
NEW
3894
        out = process.stdout.decode("utf8").strip()
×
NEW
3895
        if process.returncode == 0 and out == expected:
×
NEW
3896
            return TestResult.PASSING, f"Expected {expected}"
×
NEW
3897
        return TestResult.FAILING, f"Expected {expected}, but was {out!r}"
×
3898

3899

3900
class Matplotlib6TestGenerator:
1✔
3901
    @staticmethod
1✔
3902
    def _color(size: int) -> str:
1✔
NEW
3903
        return ",".join(str(round(random.uniform(0.05, 0.95), 3)) for _ in range(size))
×
3904

3905
    def make_failing(self) -> str:
1✔
NEW
3906
        size = random.choice([3, 4])
×
3907
        # n equal to the colour's component count triggers the misclassification.
NEW
3908
        return f"{size} {self._color(size)}"
×
3909

3910
    def make_passing(self) -> str:
1✔
NEW
3911
        size = random.choice([3, 4])
×
NEW
3912
        n = random.choice([x for x in range(2, 9) if x != size])
×
NEW
3913
        return f"{n} {self._color(size)}"
×
3914

3915

3916
class Matplotlib6SystemtestGenerator(
1✔
3917
    SystemtestGenerator, Matplotlib6TestGenerator
3918
):
3919
    def generate_failing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
3920
        return self.make_failing(), TestResult.FAILING
×
3921

3922
    def generate_passing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
3923
        return self.make_passing(), TestResult.PASSING
×
3924

3925

3926
class Matplotlib6UnittestGenerator(
1✔
3927
    python.PythonGenerator, UnittestGenerator, Matplotlib6TestGenerator
3928
):
3929
    def get_imports(self) -> List[ast.stmt]:
1✔
NEW
3930
        return ast.parse(
×
3931
            "import matplotlib\n"
3932
            "matplotlib.use('Agg')\n"
3933
            "import numpy as np\n"
3934
            "import matplotlib.pyplot as plt\n"
3935
        ).body
3936

3937
    @staticmethod
1✔
3938
    def _assert(n: int, comps: List[float]) -> List[ast.stmt]:
1✔
NEW
3939
        src = (
×
3940
            "fig, ax = plt.subplots()\n"
3941
            f"coll = ax.scatter(np.ones({n}), range({n}), c=[{list(comps)!r}])\n"
3942
            "self.assertEqual(True, coll.get_array() is None)\n"
3943
            "plt.close(fig)\n"
3944
        )
NEW
3945
        return ast.parse(src).body
×
3946

3947
    def generate_failing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
3948
        size = random.choice([3, 4])
×
NEW
3949
        comps = [round(random.uniform(0.05, 0.95), 3) for _ in range(size)]
×
NEW
3950
        test = self.get_empty_test()
×
NEW
3951
        test.body = self._assert(size, comps)
×
NEW
3952
        return test, TestResult.FAILING
×
3953

3954
    def generate_passing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
3955
        size = random.choice([3, 4])
×
NEW
3956
        n = random.choice([x for x in range(2, 9) if x != size])
×
NEW
3957
        comps = [round(random.uniform(0.05, 0.95), 3) for _ in range(size)]
×
NEW
3958
        test = self.get_empty_test()
×
NEW
3959
        test.body = self._assert(n, comps)
×
NEW
3960
        return test, TestResult.PASSING
×
3961

3962

3963
grammar_6: Grammar = clean_up(
1✔
3964
    dict(
3965
        {
3966
            "<start>": ["<number> <color>"],
3967
            "<color>": ["<c>,<c>,<c>", "<c>,<c>,<c>,<c>"],
3968
            "<c>": ["<float>"],
3969
        },
3970
        **FLOAT,
3971
    )
3972
)
3973

3974
assert is_valid_grammar(grammar_6)
1✔
3975

3976

3977
# ======================================================================
3978
# bug_22: ``Axes.hist`` did not run the *bins* through unit conversion, so
3979
# passing ``datetime`` bin edges raised ``TypeError`` (comparing float to
3980
# datetime).  The fix converts non-scalar ``bins`` with ``convert_xunits``.
3981
#
3982
# System-test format:  ``<kind> <edges>`` where ``<kind>`` is ``datetime`` or
3983
# ``numeric`` and ``<edges>`` is a comma-separated list of ``YYYY-MM-DD`` dates.
3984
# The harness histograms a fixed datetime dataset with those edges (as raw
3985
# datetimes or as ``date2num`` numbers) and prints the number of returned bins.
3986
# The correct (fixed) count equals the number of edges; ``datetime`` bins trigger
3987
# the fault (buggy raises -> no output).
3988
# ======================================================================
3989

3990

3991
class Matplotlib22API(MatplotlibAPI):
1✔
3992
    def oracle(self, args: Any) -> Tuple[TestResult, str]:
1✔
NEW
3993
        if args is None:
×
NEW
3994
            return TestResult.UNDEFINED, "No process finished"
×
NEW
3995
        process: subprocess.CompletedProcess = args
×
NEW
3996
        try:
×
NEW
3997
            kind = process.args[2]
×
NEW
3998
            edges = process.args[3].split(",")
×
NEW
3999
            if kind not in ("datetime", "numeric") or len(edges) < 2:
×
NEW
4000
                raise ValueError("bad args")
×
NEW
4001
        except (IndexError, ValueError):
×
NEW
4002
            return TestResult.UNDEFINED, "Malformed test input"
×
NEW
4003
        expected = str(len(edges))
×
NEW
4004
        out = process.stdout.decode("utf8").strip()
×
NEW
4005
        if process.returncode == 0 and out == expected:
×
NEW
4006
            return TestResult.PASSING, f"Expected {expected}"
×
NEW
4007
        return TestResult.FAILING, f"Expected {expected}, but was {out!r}"
×
4008

4009

4010
class Matplotlib22TestGenerator:
1✔
4011
    @staticmethod
1✔
4012
    def _edges() -> List[str]:
1✔
4013
        # 2-4 distinct sorted dates within 2019.
NEW
4014
        import datetime as _dt
×
4015

NEW
4016
        n = random.randint(2, 4)
×
NEW
4017
        days = sorted(random.sample(range(1, 360), n))
×
NEW
4018
        base = _dt.date(2019, 1, 1)
×
NEW
4019
        return [(base + _dt.timedelta(days=d)).strftime("%Y-%m-%d") for d in days]
×
4020

4021
    def make_failing(self) -> str:
1✔
NEW
4022
        return f"datetime {','.join(self._edges())}"
×
4023

4024
    def make_passing(self) -> str:
1✔
NEW
4025
        return f"numeric {','.join(self._edges())}"
×
4026

4027

4028
class Matplotlib22SystemtestGenerator(
1✔
4029
    SystemtestGenerator, Matplotlib22TestGenerator
4030
):
4031
    def generate_failing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
4032
        return self.make_failing(), TestResult.FAILING
×
4033

4034
    def generate_passing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
4035
        return self.make_passing(), TestResult.PASSING
×
4036

4037

4038
_HIST_DATA = (
1✔
4039
    "[[datetime.datetime(2019, 1, 5), datetime.datetime(2019, 1, 11), "
4040
    "datetime.datetime(2019, 2, 1), datetime.datetime(2019, 3, 1)], "
4041
    "[datetime.datetime(2019, 1, 11), datetime.datetime(2019, 2, 5), "
4042
    "datetime.datetime(2019, 2, 18), datetime.datetime(2019, 3, 1)]]"
4043
)
4044

4045

4046
class Matplotlib22UnittestGenerator(
1✔
4047
    python.PythonGenerator, UnittestGenerator, Matplotlib22TestGenerator
4048
):
4049
    def get_imports(self) -> List[ast.stmt]:
1✔
NEW
4050
        return ast.parse(
×
4051
            "import datetime\n"
4052
            "import matplotlib\n"
4053
            "matplotlib.use('Agg')\n"
4054
            "import matplotlib as mpl\n"
4055
            "import matplotlib.pyplot as plt\n"
4056
        ).body
4057

4058
    @staticmethod
1✔
4059
    def _assert(kind: str, edges: List[str]) -> List[ast.stmt]:
1✔
NEW
4060
        expected = len(edges)
×
NEW
4061
        edge_exprs = ", ".join(
×
4062
            f"datetime.datetime.strptime({e!r}, '%Y-%m-%d')" for e in edges
4063
        )
NEW
4064
        bins = (
×
4065
            f"[{edge_exprs}]"
4066
            if kind == "datetime"
4067
            else f"mpl.dates.date2num([{edge_exprs}])"
4068
        )
NEW
4069
        src = (
×
4070
            "fig, ax = plt.subplots()\n"
4071
            f"data = {_HIST_DATA}\n"
4072
            f"_, bins, _ = ax.hist(data, bins={bins}, stacked=True)\n"
4073
            f"self.assertEqual({expected!r}, len(bins))\n"
4074
            "plt.close(fig)\n"
4075
        )
NEW
4076
        return ast.parse(src).body
×
4077

4078
    def generate_failing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
4079
        test = self.get_empty_test()
×
NEW
4080
        test.body = self._assert("datetime", self._edges())
×
NEW
4081
        return test, TestResult.FAILING
×
4082

4083
    def generate_passing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
4084
        test = self.get_empty_test()
×
NEW
4085
        test.body = self._assert("numeric", self._edges())
×
NEW
4086
        return test, TestResult.PASSING
×
4087

4088

4089
grammar_22: Grammar = clean_up(
1✔
4090
    {
4091
        "<start>": ["<kind> <edges>"],
4092
        "<kind>": ["datetime", "numeric"],
4093
        "<edges>": [
4094
            "<date>,<date>",
4095
            "<date>,<date>,<date>",
4096
            "<date>,<date>,<date>,<date>",
4097
        ],
4098
        "<date>": ["2019-<two>-<two>"],
4099
        "<two>": ["<d><d>"],
4100
        "<d>": [str(i) for i in range(10)],
4101
    }
4102
)
4103

4104
assert is_valid_grammar(grammar_22)
1✔
4105

4106

4107
# ======================================================================
4108
# bug_23: ``_AxesBase.apply_aspect`` with ``adjustable='datalim'`` on a
4109
# *nonlinear* scale forwarded the data limits through ``trf.inverted().transform``
4110
# instead of ``trf.transform``, so the aspect-driven limit adjustment silently did
4111
# nothing.  The fix uses the forward transform.
4112
#
4113
# System-test format:  ``<mode> <xlo> <xhi>`` where ``<mode>`` is ``datalim`` or
4114
# ``box`` and ``(xlo, xhi)`` is the log x-range.  The harness builds a square
4115
# log-x / logit-y axes with ``aspect=1``, applies the aspect and prints whether
4116
# the x-limits stayed unchanged.  With ``datalim`` the limits should change (the
4117
# correct answer is ``False``); the buggy build leaves them unchanged.  ``box``
4118
# never changes the data limits (answer ``True``).
4119
# ======================================================================
4120

4121

4122
class Matplotlib23API(MatplotlibAPI):
1✔
4123
    def oracle(self, args: Any) -> Tuple[TestResult, str]:
1✔
NEW
4124
        if args is None:
×
NEW
4125
            return TestResult.UNDEFINED, "No process finished"
×
NEW
4126
        process: subprocess.CompletedProcess = args
×
NEW
4127
        try:
×
NEW
4128
            mode = process.args[2]
×
NEW
4129
            float(process.args[3])
×
NEW
4130
            float(process.args[4])
×
NEW
4131
            if mode not in ("datalim", "box"):
×
NEW
4132
                raise ValueError("bad mode")
×
NEW
4133
        except (IndexError, ValueError):
×
NEW
4134
            return TestResult.UNDEFINED, "Malformed test input"
×
NEW
4135
        expected = str(mode == "box")  # box leaves data limits unchanged
×
NEW
4136
        out = process.stdout.decode("utf8").strip()
×
NEW
4137
        if process.returncode == 0 and out == expected:
×
NEW
4138
            return TestResult.PASSING, f"Expected {expected}"
×
NEW
4139
        return TestResult.FAILING, f"Expected {expected}, but was {out!r}"
×
4140

4141

4142
class Matplotlib23TestGenerator:
1✔
4143
    @staticmethod
1✔
4144
    def _xrange() -> Tuple[float, float]:
1✔
4145
        # A *wide* range (>2 decades) so the aspect requires the limits to be
4146
        # *shrunk*: that is the direction the buggy inverted-transform fails to
4147
        # apply (a too-narrow range would be expanded even by the buggy code).
NEW
4148
        xlo = round(random.uniform(0.5, 2.0), 3)
×
NEW
4149
        xhi = round(xlo * random.uniform(150.0, 400.0), 3)
×
NEW
4150
        return xlo, xhi
×
4151

4152
    def make_failing(self) -> str:
1✔
NEW
4153
        xlo, xhi = self._xrange()
×
NEW
4154
        return f"datalim {xlo} {xhi}"
×
4155

4156
    def make_passing(self) -> str:
1✔
NEW
4157
        xlo, xhi = self._xrange()
×
NEW
4158
        return f"box {xlo} {xhi}"
×
4159

4160

4161
class Matplotlib23SystemtestGenerator(
1✔
4162
    SystemtestGenerator, Matplotlib23TestGenerator
4163
):
4164
    def generate_failing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
4165
        return self.make_failing(), TestResult.FAILING
×
4166

4167
    def generate_passing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
4168
        return self.make_passing(), TestResult.PASSING
×
4169

4170

4171
class Matplotlib23UnittestGenerator(
1✔
4172
    python.PythonGenerator, UnittestGenerator, Matplotlib23TestGenerator
4173
):
4174
    def get_imports(self) -> List[ast.stmt]:
1✔
NEW
4175
        return ast.parse(
×
4176
            "import matplotlib\n"
4177
            "matplotlib.use('Agg')\n"
4178
            "import matplotlib.pyplot as plt\n"
4179
        ).body
4180

4181
    @staticmethod
1✔
4182
    def _assert(mode: str, xlo: float, xhi: float) -> List[ast.stmt]:
1✔
NEW
4183
        expected = mode == "box"
×
NEW
4184
        src = (
×
4185
            "fig = plt.figure(figsize=(10, 10))\n"
4186
            "ax = fig.add_axes([.1, .1, .8, .8])\n"
4187
            "ax.plot([.4, .6], [.4, .6])\n"
4188
            f"ax.set(xscale='log', xlim=({xlo}, {xhi}), yscale='logit', "
4189
            f"ylim=(1 / 101, 1 / 11), aspect=1, adjustable={mode!r})\n"
4190
            "ax.margins(0)\n"
4191
            "ax.apply_aspect()\n"
4192
            "cur = ax.get_xlim()\n"
4193
            f"unchanged = abs(cur[0] - {xlo}) < 1e-06 and abs(cur[1] - {xhi}) < 1e-06\n"
4194
            f"self.assertEqual({expected!r}, unchanged)\n"
4195
            "plt.close(fig)\n"
4196
        )
NEW
4197
        return ast.parse(src).body
×
4198

4199
    def generate_failing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
4200
        xlo, xhi = self._xrange()
×
NEW
4201
        test = self.get_empty_test()
×
NEW
4202
        test.body = self._assert("datalim", xlo, xhi)
×
NEW
4203
        return test, TestResult.FAILING
×
4204

4205
    def generate_passing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
4206
        xlo, xhi = self._xrange()
×
NEW
4207
        test = self.get_empty_test()
×
NEW
4208
        test.body = self._assert("box", xlo, xhi)
×
NEW
4209
        return test, TestResult.PASSING
×
4210

4211

4212
grammar_23: Grammar = clean_up(
1✔
4213
    dict(
4214
        {
4215
            "<start>": ["<mode> <float> <float>"],
4216
            "<mode>": ["datalim", "box"],
4217
        },
4218
        **FLOAT,
4219
    )
4220
)
4221

4222
assert is_valid_grammar(grammar_23)
1✔
4223

4224

4225
# ======================================================================
4226
# bug_9: ``PolarAxes.draw`` did not call ``self._unstale_viewLim()`` before
4227
# rendering, so setting the r-limits *indirectly* (e.g. via
4228
# ``yaxis.set_inverted`` + ``set_rorigin``) left the view limits stale and the
4229
# figure was drawn with the wrong radial transform.  The fix unstales the view
4230
# limits at the top of ``draw``.
4231
#
4232
# Because the fault only shows up in the *rendered* transform (the reported
4233
# limits are already correct), the harness renders the scene twice -- once with a
4234
# plain ``draw`` and once after an explicit ``_unstale_viewLim()`` -- and reports
4235
# whether the two Agg buffers are identical.  On the fixed build ``draw`` already
4236
# unstales, so the explicit call is a no-op and the buffers match (``True``); on
4237
# the buggy build the stale-limit scene renders differently (``False``).
4238
#
4239
# System-test format:  ``<scenario> <rorigin> <rmax>`` where ``<scenario>`` is
4240
# ``stale`` (the inverted-ylim + rorigin fault) or ``normal`` (a plain polar
4241
# plot whose limits are never stale).  ``stale`` triggers the fault.
4242
# ======================================================================
4243

4244

4245
class Matplotlib9API(MatplotlibAPI):
1✔
4246
    def oracle(self, args: Any) -> Tuple[TestResult, str]:
1✔
NEW
4247
        if args is None:
×
NEW
4248
            return TestResult.UNDEFINED, "No process finished"
×
NEW
4249
        process: subprocess.CompletedProcess = args
×
NEW
4250
        try:
×
NEW
4251
            scenario = process.args[2]
×
NEW
4252
            float(process.args[3])
×
NEW
4253
            float(process.args[4])
×
NEW
4254
            if scenario not in ("stale", "normal"):
×
NEW
4255
                raise ValueError("bad scenario")
×
NEW
4256
        except (IndexError, ValueError):
×
NEW
4257
            return TestResult.UNDEFINED, "Malformed test input"
×
NEW
4258
        expected = "True"  # a redundant pre-unstale must not change the render
×
NEW
4259
        out = process.stdout.decode("utf8").strip()
×
NEW
4260
        if process.returncode == 0 and out == expected:
×
NEW
4261
            return TestResult.PASSING, f"Expected {expected}"
×
NEW
4262
        return TestResult.FAILING, f"Expected {expected}, but was {out!r}"
×
4263

4264

4265
class Matplotlib9TestGenerator:
1✔
4266
    def make_failing(self) -> str:
1✔
NEW
4267
        rmax = round(random.uniform(1.5, 3.0), 3)
×
NEW
4268
        rorigin = round(rmax + random.uniform(0.5, 3.0), 3)
×
NEW
4269
        return f"stale {rorigin} {rmax}"
×
4270

4271
    def make_passing(self) -> str:
1✔
NEW
4272
        rmax = round(random.uniform(2.0, 4.0), 3)
×
NEW
4273
        rorigin = round(random.uniform(-3.0, -0.5), 3)
×
NEW
4274
        return f"normal {rorigin} {rmax}"
×
4275

4276

4277
class Matplotlib9SystemtestGenerator(
1✔
4278
    SystemtestGenerator, Matplotlib9TestGenerator
4279
):
4280
    def generate_failing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
4281
        return self.make_failing(), TestResult.FAILING
×
4282

4283
    def generate_passing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
4284
        return self.make_passing(), TestResult.PASSING
×
4285

4286

4287
class Matplotlib9UnittestGenerator(
1✔
4288
    python.PythonGenerator, UnittestGenerator, Matplotlib9TestGenerator
4289
):
4290
    def get_imports(self) -> List[ast.stmt]:
1✔
NEW
4291
        return ast.parse(
×
4292
            "import hashlib\n"
4293
            "import warnings\n"
4294
            "import matplotlib\n"
4295
            "matplotlib.use('Agg')\n"
4296
            "import numpy as np\n"
4297
            "import matplotlib.pyplot as plt\n"
4298
        ).body
4299

4300
    @staticmethod
1✔
4301
    def _assert(scenario: str, rorigin: float, rmax: float) -> List[ast.stmt]:
1✔
NEW
4302
        if scenario == "stale":
×
NEW
4303
            build = (
×
4304
                "ax.yaxis.set_inverted(True)\n"
4305
                f"    ax.plot([0, 0], [0, {rmax}], c='none')\n"
4306
                "    ax.margins(0)\n"
4307
                f"    ax.set_rorigin({rorigin})\n"
4308
            )
4309
        else:
NEW
4310
            build = (
×
4311
                "theta = np.linspace(0, 2 * np.pi, 50)\n"
4312
                f"    ax.plot(theta, np.ones(50) * {rmax} * 0.8)\n"
4313
                f"    ax.set_rlim(0, {rmax})\n"
4314
                f"    ax.set_rorigin({rorigin})\n"
4315
            )
NEW
4316
        src = (
×
4317
            "def _render(unstale):\n"
4318
            "    fig = plt.figure()\n"
4319
            "    ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], polar=True)\n"
4320
            f"    {build}"
4321
            "    if unstale:\n"
4322
            "        ax._unstale_viewLim()\n"
4323
            "    fig.canvas.draw()\n"
4324
            "    buf = np.asarray(fig.canvas.buffer_rgba()).copy()\n"
4325
            "    plt.close(fig)\n"
4326
            "    return hashlib.md5(buf.tobytes()).hexdigest()\n"
4327
            "with warnings.catch_warnings():\n"
4328
            "    warnings.simplefilter('ignore')\n"
4329
            "    self.assertEqual(True, _render(False) == _render(True))\n"
4330
        )
NEW
4331
        return ast.parse(src).body
×
4332

4333
    def generate_failing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
4334
        rmax = round(random.uniform(1.5, 3.0), 3)
×
NEW
4335
        rorigin = round(rmax + random.uniform(0.5, 3.0), 3)
×
NEW
4336
        test = self.get_empty_test()
×
NEW
4337
        test.body = self._assert("stale", rorigin, rmax)
×
NEW
4338
        return test, TestResult.FAILING
×
4339

4340
    def generate_passing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
4341
        rmax = round(random.uniform(2.0, 4.0), 3)
×
NEW
4342
        rorigin = round(random.uniform(-3.0, -0.5), 3)
×
NEW
4343
        test = self.get_empty_test()
×
NEW
4344
        test.body = self._assert("normal", rorigin, rmax)
×
NEW
4345
        return test, TestResult.PASSING
×
4346

4347

4348
grammar_9: Grammar = clean_up(
1✔
4349
    dict(
4350
        {
4351
            "<start>": ["<scenario> <float> <float>"],
4352
            "<scenario>": ["stale", "normal"],
4353
        },
4354
        **FLOAT,
4355
    )
4356
)
4357

4358
assert is_valid_grammar(grammar_9)
1✔
4359

4360

4361
# ======================================================================
4362
# bug_1: the tight-bbox save path patched the renderer's ``draw_*`` methods to
4363
# no-ops *permanently* (via ``_get_renderer(draw_disabled=True)``) and then drew
4364
# the figure with them, so a ``savefig(bbox_inches='tight')`` produced a blank
4365
# (all-white, fully-transparent) image.  The fix restores the draw methods with a
4366
# ``cbook._setattr_cm`` context manager so the real content is rendered.
4367
#
4368
# System-test format:  ``<mode> <x_size> <y_size> <dpi>`` where ``<mode>`` is
4369
# ``tight`` (save with ``bbox_inches='tight'``) or ``normal`` (plain save).  The
4370
# harness draws an ``imshow`` filling the axes and prints whether the saved image
4371
# is fully opaque *and* not entirely white.  The correct (fixed) answer is always
4372
# ``True``; ``tight`` triggers the fault (buggy saves a blank image).
4373
# ======================================================================
4374

4375

4376
class Matplotlib1API(MatplotlibAPI):
1✔
4377
    def oracle(self, args: Any) -> Tuple[TestResult, str]:
1✔
NEW
4378
        if args is None:
×
NEW
4379
            return TestResult.UNDEFINED, "No process finished"
×
NEW
4380
        process: subprocess.CompletedProcess = args
×
NEW
4381
        try:
×
NEW
4382
            mode = process.args[2]
×
NEW
4383
            int(process.args[3])
×
NEW
4384
            int(process.args[4])
×
NEW
4385
            int(process.args[5])
×
NEW
4386
            if mode not in ("tight", "normal"):
×
NEW
4387
                raise ValueError("bad mode")
×
NEW
4388
        except (IndexError, ValueError):
×
NEW
4389
            return TestResult.UNDEFINED, "Malformed test input"
×
NEW
4390
        expected = "True"  # the content is always rendered on the fixed build
×
NEW
4391
        out = process.stdout.decode("utf8").strip()
×
NEW
4392
        if process.returncode == 0 and out == expected:
×
NEW
4393
            return TestResult.PASSING, f"Expected {expected}"
×
NEW
4394
        return TestResult.FAILING, f"Expected {expected}, but was {out!r}"
×
4395

4396

4397
class Matplotlib1TestGenerator:
1✔
4398
    # Whether ``savefig(bbox_inches='tight')`` renders a blank image on the buggy
4399
    # build is sensitive to the tight-bbox pixel rounding, so the failing tests
4400
    # use the test's own aspect (y=7, dpi=100) with x-sizes empirically verified
4401
    # to reproduce the blank output on the buggy build (and which render
4402
    # correctly on the fixed build).
4403
    _BLANK_X = [
1✔
4404
        8, 9, 10, 12, 14, 15, 16, 17, 18, 19, 20, 21, 23, 24, 25, 27, 28, 29, 30,
4405
    ]
4406

4407
    @staticmethod
1✔
4408
    def _pass_dims() -> Tuple[int, int, int]:
1✔
NEW
4409
        return (
×
4410
            random.randint(6, 30),
4411
            random.randint(5, 20),
4412
            random.choice([80, 100, 120, 150]),
4413
        )
4414

4415
    def make_failing(self) -> str:
1✔
NEW
4416
        return f"tight {random.choice(self._BLANK_X)} 7 100"
×
4417

4418
    def make_passing(self) -> str:
1✔
NEW
4419
        x, y, dpi = self._pass_dims()
×
NEW
4420
        return f"normal {x} {y} {dpi}"
×
4421

4422

4423
class Matplotlib1SystemtestGenerator(
1✔
4424
    SystemtestGenerator, Matplotlib1TestGenerator
4425
):
4426
    def generate_failing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
4427
        return self.make_failing(), TestResult.FAILING
×
4428

4429
    def generate_passing_test(self) -> Tuple[str, TestResult]:
1✔
NEW
4430
        return self.make_passing(), TestResult.PASSING
×
4431

4432

4433
class Matplotlib1UnittestGenerator(
1✔
4434
    python.PythonGenerator, UnittestGenerator, Matplotlib1TestGenerator
4435
):
4436
    def get_imports(self) -> List[ast.stmt]:
1✔
NEW
4437
        return ast.parse(
×
4438
            "from io import BytesIO\n"
4439
            "import matplotlib\n"
4440
            "matplotlib.use('Agg')\n"
4441
            "import numpy as np\n"
4442
            "import matplotlib.pyplot as plt\n"
4443
            "from PIL import Image\n"
4444
        ).body
4445

4446
    @staticmethod
1✔
4447
    def _assert(mode: str, x_size: int, y_size: int, dpi: int) -> List[ast.stmt]:
1✔
NEW
4448
        save = (
×
4449
            "fig.savefig(out, bbox_inches='tight', pad_inches=0)"
4450
            if mode == "tight"
4451
            else "fig.savefig(out)"
4452
        )
NEW
4453
        src = (
×
4454
            f"fig = plt.figure(frameon=False, dpi={dpi}, "
4455
            f"figsize=({x_size} / {dpi}, {y_size} / {dpi}))\n"
4456
            "ax = plt.Axes(fig, [0.0, 0.0, 1.0, 1.0])\n"
4457
            "fig.add_axes(ax)\n"
4458
            "ax.set_axis_off()\n"
4459
            "ax.get_xaxis().set_visible(False)\n"
4460
            "ax.get_yaxis().set_visible(False)\n"
4461
            f"data = np.arange({x_size} * {y_size}).reshape({y_size}, {x_size})\n"
4462
            "ax.imshow(data)\n"
4463
            "out = BytesIO()\n"
4464
            f"{save}\n"
4465
            "out.seek(0)\n"
4466
            "im = np.asarray(Image.open(out))\n"
4467
            "ok = bool((im[:, :, 3] == 255).all() and not (im[:, :, :3] == 255).all())\n"
4468
            "self.assertEqual(True, ok)\n"
4469
            "plt.close(fig)\n"
4470
        )
NEW
4471
        return ast.parse(src).body
×
4472

4473
    def generate_failing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
4474
        test = self.get_empty_test()
×
NEW
4475
        test.body = self._assert("tight", random.choice(self._BLANK_X), 7, 100)
×
NEW
4476
        return test, TestResult.FAILING
×
4477

4478
    def generate_passing_test(self) -> Tuple[ast.FunctionDef, TestResult]:
1✔
NEW
4479
        x, y, dpi = self._pass_dims()
×
NEW
4480
        test = self.get_empty_test()
×
NEW
4481
        test.body = self._assert("normal", x, y, dpi)
×
NEW
4482
        return test, TestResult.PASSING
×
4483

4484

4485
grammar_1: Grammar = clean_up(
1✔
4486
    dict(
4487
        {
4488
            "<start>": ["<mode> <number> <number> <number>"],
4489
            "<mode>": ["tight", "normal"],
4490
        },
4491
        **NUMBER,
4492
    )
4493
)
4494

4495
assert is_valid_grammar(grammar_1)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc