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

mozilla / mozregression / 14641798044

24 Apr 2025 12:38PM CUT coverage: 87.386%. First build
14641798044

Pull #1983

github

web-flow
Merge da7e48128 into 807564865
Pull Request #1983: Bug 1763188 - Add Snap support using TC builds

59 of 132 new or added lines in 4 files covered. (44.7%)

2577 of 2949 relevant lines covered (87.39%)

8.54 hits per line

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

89.04
/mozregression/cli.py
1
"""
2
This module parses and checks the command line with :func:`cli` and return a
3
:class:`Configuration` object that hold information for running the
4
application.
5

6
:func:`cli` is intended to be the only public interface of this module.
7
"""
8

9
from __future__ import absolute_import, print_function
9✔
10

11
import datetime
9✔
12
import os
9✔
13
import re
9✔
14
from argparse import SUPPRESS, Action, ArgumentParser
9✔
15

16
import mozinfo
9✔
17
import mozprofile
9✔
18
from mozlog.structuredlog import get_default_logger
9✔
19

20
from mozregression import __version__
9✔
21
from mozregression.branches import get_name
9✔
22
from mozregression.config import DEFAULT_CONF_FNAME, get_config, write_config
9✔
23
from mozregression.dates import is_date_or_datetime, parse_date, to_datetime
9✔
24
from mozregression.errors import DateFormatError, MozRegressionError, UnavailableRelease
9✔
25
from mozregression.fetch_configs import REGISTRY as FC_REGISTRY
9✔
26
from mozregression.fetch_configs import create_config
9✔
27
from mozregression.log import colorize, init_logger
9✔
28
from mozregression.releases import (
9✔
29
    date_of_release,
30
    formatted_valid_release_dates,
31
    tag_of_beta,
32
    tag_of_release,
33
)
34
from mozregression.tc_authenticate import tc_authenticate
9✔
35

36

37
class _StopAction(Action):
9✔
38
    def __init__(self, option_strings, dest=SUPPRESS, default=SUPPRESS, help=None):
9✔
39
        super(_StopAction, self).__init__(
9✔
40
            option_strings=option_strings,
41
            dest=dest,
42
            default=default,
43
            nargs=0,
44
            help=help,
45
        )
46

47
    def __call__(self, parser, namespace, values, option_string=None):
9✔
48
        raise NotImplementedError
49

50

51
class ListReleasesAction(_StopAction):
9✔
52
    def __call__(self, parser, namespace, values, option_string=None):
9✔
53
        print(formatted_valid_release_dates())
9✔
54
        parser.exit()
9✔
55

56

57
class WriteConfigAction(_StopAction):
9✔
58
    def __call__(self, parser, namespace, values, option_string=None):
9✔
59
        write_config(DEFAULT_CONF_FNAME)
9✔
60
        parser.exit()
9✔
61

62

63
class ListBuildTypesAction(_StopAction):
9✔
64
    def __call__(self, parser, namespace, values, option_string=None):
9✔
65
        for name in FC_REGISTRY.names():
9✔
66
            print("%s:" % name)
9✔
67
            klass = FC_REGISTRY.get(name)
9✔
68
            for btype in klass.BUILD_TYPES:
9✔
69
                print("  %s" % btype)
9✔
70
        parser.exit()
9✔
71

72

73
def parse_args(argv=None, defaults=None):
9✔
74
    """
75
    Parse command line options.
76
    """
77
    parser = create_parser(defaults=defaults)
9✔
78
    return parser.parse_args(argv)
9✔
79

80

81
def create_parser(defaults):
9✔
82
    """
83
    Create the mozregression command line parser (ArgumentParser instance).
84
    """
85
    usage = (
9✔
86
        "\n"
87
        " %(prog)s [OPTIONS]"
88
        " [--bad DATE|BUILDID|RELEASE|CHANGESET]"
89
        " [--good DATE|BUILDID|RELEASE|CHANGESET]"
90
        "\n"
91
        " %(prog)s [OPTIONS] --launch DATE|BUILDID|RELEASE|CHANGESET"
92
        "\n"
93
        " %(prog)s --list-build-types"
94
        "\n"
95
        " %(prog)s --list-releases"
96
        "\n"
97
        " %(prog)s --write-conf"
98
    )
99

100
    parser = ArgumentParser(usage=usage)
9✔
101
    parser.add_argument(
9✔
102
        "--version",
103
        action="version",
104
        version=__version__,
105
        help=("print the mozregression version number and" " exits."),
106
    )
107

108
    parser.add_argument(
9✔
109
        "-b",
110
        "--bad",
111
        metavar="DATE|BUILDID|RELEASE|CHANGESET",
112
        help=(
113
            "first known bad build, default is today."
114
            " It can be a date (YYYY-MM-DD), a build id,"
115
            " a release number or a changeset."
116
        ),
117
    )
118

119
    parser.add_argument(
9✔
120
        "-g",
121
        "--good",
122
        metavar="DATE|BUILDID|RELEASE|CHANGESET",
123
        help=("last known good build. Same possible values" " as the --bad option."),
124
    )
125

126
    parser.add_argument(
9✔
127
        "--list-releases",
128
        action=ListReleasesAction,
129
        help="list all known releases and exit",
130
    )
131

132
    parser.add_argument(
9✔
133
        "-B",
134
        "--build-type",
135
        default=defaults["build-type"],
136
        help=(
137
            "Build type to use, e.g. opt, debug. "
138
            "See --list-build-types for available values. "
139
            "Defaults to shippable for desktop Fx, opt for "
140
            "everything else."
141
        ),
142
    )
143

144
    parser.add_argument(
9✔
145
        "--list-build-types",
146
        action=ListBuildTypesAction,
147
        help="List available build types combinations.",
148
    )
149

150
    parser.add_argument(
9✔
151
        "--find-fix",
152
        action="store_true",
153
        help="Search for a bug fix instead of a regression.",
154
    )
155

156
    parser.add_argument(
9✔
157
        "-e",
158
        "--addon",
159
        dest="addons",
160
        action="append",
161
        default=[],
162
        metavar="PATH1",
163
        help="addon to install; repeat for multiple addons.",
164
    )
165

166
    parser.add_argument(
9✔
167
        "-p",
168
        "--profile",
169
        default=defaults["profile"],
170
        metavar="PATH",
171
        help="profile to use with nightlies.",
172
    )
173

174
    parser.add_argument(
9✔
175
        "--adb-profile-dir",
176
        dest="adb_profile_dir",
177
        default=defaults["adb-profile-dir"],
178
        help=(
179
            "Path to use on android devices for storing"
180
            " the profile. Generally you should not need"
181
            " to specify this, and an appropriate path"
182
            " will be used. Specifying this to a value,"
183
            " e.g. '/sdcard/tests' will forcibly try to create"
184
            " the profile inside that folder."
185
        ),
186
    )
187

188
    parser.add_argument(
9✔
189
        "--profile-persistence",
190
        choices=("clone", "clone-first", "reuse"),
191
        default=defaults["profile-persistence"],
192
        help=(
193
            "Persistence of the used profile. Before"
194
            " each tested build, a profile is used. If"
195
            " the value of this option is 'clone', each"
196
            " test uses a fresh clone. If the value is"
197
            " 'clone-first', the profile is cloned once"
198
            " then reused for all builds during the "
199
            " bisection. If the value is 'reuse', the given"
200
            " profile is directly used. Note that the"
201
            " profile might be modified by mozregression."
202
            " Defaults to %(default)s."
203
        ),
204
    )
205

206
    parser.add_argument(
207
        "-a",
208
        "--arg",
209
        dest="cmdargs",
210
        action="append",
211
        default=[],
212
        metavar="ARG1",
213
        help=(
214
            "a command-line argument to pass to the"
215
            " application; repeat for multiple arguments."
216
            " Use --arg='-option' to pass in options"
217
            " starting with `-`."
218
        ),
219
    )
220

221
    parser.add_argument(
222
        "--pref",
223
        nargs="*",
224
        dest="prefs",
225
        help=(
226
            " A preference to set. Must be a key-value pair"
227
            " separated by a ':'. Note that if your"
228
            " preference is of type float, you should"
229
            " pass it as a string, e.g.:"
230
            " --pref \"layers.low-precision-opacity:'0.0'\""
231
        ),
232
    )
233

234
    parser.add_argument(
9✔
235
        "--preferences",
236
        nargs="*",
237
        dest="prefs_files",
238
        help=(
239
            "read preferences from a JSON or INI file. For"
240
            " INI, use 'file.ini:section' to specify a"
241
            " particular section."
242
        ),
243
    )
244

245
    parser.add_argument(
9✔
246
        "-n",
247
        "--app",
248
        choices=FC_REGISTRY.names(),
249
        default=defaults["app"],
250
        help="application name. Default: %(default)s.",
251
    )
252

253
    parser.add_argument(
9✔
254
        "--lang",
255
        metavar="[ar|es-ES|he|ja|zh-CN|...]",
256
        help=("build language. Only valid when app is firefox-l10n or thunderbird-l10n."),
257
    )
258

259
    parser.add_argument(
9✔
260
        "--repo",
261
        metavar="[autoland|mozilla-beta...]",
262
        default=defaults["repo"],
263
        help="repository name used for the bisection.",
264
    )
265

266
    parser.add_argument(
9✔
267
        "--bits",
268
        choices=("32", "64"),
269
        default=defaults["bits"],
270
        help=(
271
            "force 32 or 64 bit version (only applies to"
272
            " x86_64 boxes). Default: %s bits." % defaults["bits"]
273
            or mozinfo.bits
274
        ),
275
    )
276

277
    parser.add_argument(
9✔
278
        "--arch",
279
        choices=(
280
            "aarch64",
281
            "arm",
282
            "arm64-v8a",
283
            "armeabi-v7a",
284
            "x86",
285
            "x86_64",
286
        ),
287
        default=None,
288
        help=("Force alternate build (applies to Firefox, Firefox-l10n, GVE, Fenix, and Focus)."),
289
    )
290

291
    parser.add_argument(
9✔
292
        "-c",
293
        "--command",
294
        help=(
295
            "Test command to evaluate builds automatically."
296
            " A return code of 0 will evaluate the build as"
297
            " good, and any other value as bad."
298
            " Variables like {binary} can be used, which"
299
            " will be replaced with their value as retrieved"
300
            " by the actual build."
301
        ),
302
    )
303

304
    parser.add_argument(
9✔
305
        "--persist",
306
        default=defaults["persist"],
307
        help=(
308
            "the directory in which downloaded files are" " to persist. Defaults to %(default)r."
309
        ),
310
    )
311

312
    parser.add_argument(
9✔
313
        "--persist-size-limit",
314
        type=float,
315
        default=defaults["persist-size-limit"],
316
        help=(
317
            "Size limit for the persist directory in"
318
            " gigabytes (GiB). When the limit is reached,"
319
            " old builds are removed. 0 means no limit. Note"
320
            " that at least 5 build files are kept,"
321
            " regardless of this value."
322
            " Defaults to %(default)s."
323
        ),
324
    )
325

326
    parser.add_argument(
9✔
327
        "--http-timeout",
328
        type=float,
329
        default=float(defaults["http-timeout"]),
330
        help=(
331
            "Timeout in seconds to abort requests when there"
332
            " is no activity from the server. Default to"
333
            " %(default)s seconds - increase this if you"
334
            " are under a really slow network."
335
        ),
336
    )
337

338
    parser.add_argument(
9✔
339
        "--no-background-dl",
340
        action="store_false",
341
        dest="background_dl",
342
        default=(defaults["no-background-dl"].lower() not in ("1", "yes", "true")),
343
        help=(
344
            "Do not download next builds in the background" " while evaluating the current build."
345
        ),
346
    )
347

348
    parser.add_argument(
9✔
349
        "--background-dl-policy",
350
        choices=("cancel", "keep"),
351
        default=defaults["background-dl-policy"],
352
        help=(
353
            "Policy to use for background downloads."
354
            ' Possible values are "cancel" to cancel all'
355
            ' pending background downloads or "keep" to keep'
356
            " downloading them when persist mode is enabled."
357
            " The default is %(default)s."
358
        ),
359
    )
360

361
    parser.add_argument(
9✔
362
        "--approx-policy",
363
        choices=("auto", "none"),
364
        default=defaults["approx-policy"],
365
        help=(
366
            "Policy to reuse approximate persistent builds"
367
            " instead of downloading the accurate ones."
368
            " When auto, mozregression will try its best to"
369
            " reuse the files, meaning that for 7 days of"
370
            " bisection range it will try to reuse a build"
371
            " which date approximates the build to download"
372
            " by one day (before or after). Use none to"
373
            " disable this behavior."
374
            " Defaults to %(default)s."
375
        ),
376
    )
377

378
    parser.add_argument(
9✔
379
        "--launch",
380
        metavar="DATE|BUILDID|RELEASE|CHANGESET",
381
        help=("Launch only one specific build. Same possible" " values as the --bad option."),
382
    )
383

384
    parser.add_argument(
9✔
385
        "-P",
386
        "--process-output",
387
        choices=("none", "stdout"),
388
        default=defaults["process-output"],
389
        help=(
390
            "Manage process output logging. Set to stdout by"
391
            " default when the build type is not 'opt'."
392
        ),
393
    )
394

395
    parser.add_argument(
9✔
396
        "-M",
397
        "--mode",
398
        choices=("classic", "no-first-check"),
399
        default=defaults["mode"],
400
        help=(
401
            "bisection mode. 'classic' will check for the"
402
            " first good and bad builds to really be good"
403
            " and bad, and 'no-first-check' won't. Defaults"
404
            " to %(default)s."
405
        ),
406
    )
407

408
    parser.add_argument(
9✔
409
        "--archive-base-url",
410
        default=defaults["archive-base-url"],
411
        help=("Base url used to find the archived builds." " Defaults to %(default)s"),
412
    )
413

414
    parser.add_argument(
9✔
415
        "--write-config",
416
        action=WriteConfigAction,
417
        help="Helps to write the configuration file.",
418
    )
419

420
    parser.add_argument(
9✔
421
        "--allow-sudo",
422
        action="store_true",
423
        help=(
424
            "[Snap] Allow the use of sudo for Snap install/remove operations (otherwise,"
425
            " you will be prompted on each)"
426
        ),
427
    )
428

429
    parser.add_argument(
9✔
430
        "--disable-snap-connect",
431
        action="store_true",
432
        help="[Snap] Do not automatically perform 'snap connect'",
433
    )
434

435
    parser.add_argument("--debug", "-d", action="store_true", help="Show the debug output.")
9✔
436

437
    return parser
9✔
438

439

440
def parse_bits(option_bits):
9✔
441
    """
442
    Returns the correctly typed bits.
443
    """
444
    if option_bits == "32":
9✔
445
        return 32
9✔
446
    else:
447
        # if 64 bits is passed on a 32 bit system, it won't be honored
448
        return mozinfo.bits
9✔
449

450

451
def preferences(prefs_files, prefs_args, logger):
9✔
452
    """
453
    profile preferences
454
    """
455
    # object that will hold the preferences
456
    prefs = mozprofile.prefs.Preferences()
9✔
457

458
    # add preferences files
459
    if prefs_files:
9✔
460
        for prefs_file in prefs_files:
9✔
461
            prefs.add_file(prefs_file)
9✔
462

463
    separator = ":"
9✔
464
    cli_prefs = []
9✔
465
    if prefs_args:
9✔
466
        for pref in prefs_args:
9✔
467
            if separator not in pref:
9✔
468
                if logger:
9✔
469
                    if "=" in pref:
×
470
                        logger.warning('Pref %s has an "=", did you mean to use ":"?' % pref)
×
471
                    logger.info('Dropping pref %s for missing separator ":"' % pref)
×
472
                continue
6✔
473
            cli_prefs.append(pref.split(separator, 1))
9✔
474

475
    # string preferences
476
    prefs.add(cli_prefs, cast=True)
9✔
477

478
    return prefs()
9✔
479

480

481
def get_default_date_range(fetch_config):
9✔
482
    """
483
    Compute the default date range (first, last) to bisect.
484
    """
485
    last_date = datetime.date.today()
9✔
486
    first_date = datetime.date.today() - datetime.timedelta(days=365)
9✔
487

488
    return first_date, last_date
9✔
489

490

491
class Configuration(object):
9✔
492
    """
493
    Holds the configuration extracted from the command line + configuration file.
494

495
    This is usually instantiated by calling :func:`cli`.
496

497
    The constructor only initializes the `logger`.
498

499
    The configuration should not be used (except for the logger attribute)
500
    until :meth:`validate` is called.
501

502
    :attr logger: the mozlog logger, created using the command line options
503
    :attr options: the raw command line options
504
    :attr action: the action that the user want to do. This is a string
505
                  ("bisect_integration" or "bisect_nightlies")
506
    :attr fetch_config: the fetch_config instance, required to find
507
                        information about a build
508
    """
509

510
    def __init__(self, options, config):
9✔
511
        self.options = options
9✔
512
        self.logger = init_logger(debug=options.debug)
9✔
513
        # allow to filter process output based on the user option
514
        if options.process_output is None:
9✔
515
            # process_output not user defined
516
            log_process_output = options.build_type != ""
9✔
517
        else:
518
            log_process_output = options.process_output == "stdout"
9✔
519
        get_default_logger("process").component_filter = lambda data: (
9✔
520
            data if log_process_output else None
521
        )
522

523
        # filter some mozversion log lines
524
        re_ignore_mozversion_line = re.compile(
9✔
525
            r"^(platform_.+|application_vendor|application_remotingname"
526
            r"|application_id|application_display_name): .+"
527
        )
528
        get_default_logger("mozversion").component_filter = lambda data: (
9✔
529
            None if re_ignore_mozversion_line.match(data["message"]) else data
530
        )
531

532
        self.enable_telemetry = config["enable-telemetry"] not in ("no", "false", 0)
9✔
533

534
        self.action = None
9✔
535
        self.fetch_config = None
9✔
536

537
    def _convert_to_bisect_arg(self, value):
9✔
538
        """
539
        Transform a string value to a date or datetime if it looks like it.
540
        """
541
        try:
9✔
542
            value = parse_date(value)
9✔
543
        except DateFormatError:
9✔
544
            try:
9✔
545
                repo = self.options.repo
9✔
546
                if get_name(repo) == "mozilla-release" or (
9✔
547
                    not repo and re.match(r"^\d+\.\d(\.\d)?$", value)
548
                ):
549
                    new_value = tag_of_release(value)
9✔
550
                    if not repo:
9✔
551
                        self.logger.info("Assuming repo mozilla-release")
9✔
552
                        self.fetch_config.set_repo("mozilla-release")
9✔
553
                    self.logger.info("Using tag %s for release %s" % (new_value, value))
9✔
554
                    value = new_value
9✔
555
                elif get_name(repo) == "mozilla-beta" or (
9✔
556
                    not repo and re.match(r"^\d+\.0b\d+$", value)
557
                ):
558
                    new_value = tag_of_beta(value)
9✔
559
                    if not repo:
9✔
560
                        self.logger.info("Assuming repo mozilla-beta")
9✔
561
                        self.fetch_config.set_repo("mozilla-beta")
9✔
562
                    self.logger.info("Using tag %s for release %s" % (new_value, value))
9✔
563
                    value = new_value
9✔
564
                else:
565
                    new_value = parse_date(date_of_release(value))
9✔
566
                    self.logger.info("Using date %s for release %s" % (new_value, value))
9✔
567
                    value = new_value
9✔
568
            except UnavailableRelease:
9✔
569
                self.logger.info("%s is not a release, assuming it's a hash..." % value)
9✔
570
        return value
9✔
571

572
    def validate(self):
9✔
573
        """
574
        Validate the options, define the `action` and `fetch_config` that
575
        should be used to run the application.
576
        """
577
        options = self.options
9✔
578

579
        arch_options = {
9✔
580
            "firefox": [
581
                "aarch64",
582
                "x86",
583
                "x86_64",
584
            ],
585
            "firefox-l10n": [
586
                "aarch64",
587
                "x86",
588
                "x86_64",
589
            ],
590
            "gve": [
591
                "aarch64",
592
                "arm",
593
                "x86_64",
594
            ],
595
            "fenix": [
596
                "arm64-v8a",
597
                "armeabi-v7a",
598
                "x86",
599
                "x86_64",
600
            ],
601
            "focus": [
602
                "arm64-v8a",
603
                "armeabi-v7a",
604
                "x86",
605
                "x86_64",
606
            ],
607
            "firefox-snap": [
608
                "aarch64",  # will be morphed into arm64
609
                "arm",  # will be morphed into armhf
610
                "x86_64",  # will be morphed into amd64
611
            ],
612
        }
613

614
        user_defined_bits = options.bits is not None
9✔
615
        options.bits = parse_bits(options.bits or mozinfo.bits)
9✔
616

617
        if options.arch is not None:
9✔
618
            if user_defined_bits:
×
619
                self.logger.warning(
620
                    "--arch and --bits are passed together. --arch will be preferred."
621
                )
622

623
            if options.app not in arch_options:
×
624
                self.logger.warning(f"--arch ignored for {options.app}.")
×
625
                options.arch = None
×
626
            elif options.app in ("firefox", "firefox-l10n") and mozinfo.os == "mac":
×
627
                self.logger.warning(
×
628
                    "--arch ignored for Firefox for macOS as it uses unified binary."
629
                )
630
                options.arch = None
×
631
            elif options.arch not in arch_options[options.app]:
×
632
                raise MozRegressionError(
×
633
                    f"Invalid arch ({options.arch}) specified for app ({options.app}). "
634
                    f"Valid options are: {', '.join(arch_options[options.app])}."
635
                )
636
        elif options.app in ("fenix", "focus"):
9✔
637
            raise MozRegressionError(
×
638
                f"`--arch` required for specified app ({options.app}). "
639
                f"Please specify one of {', '.join(arch_options[options.app])}."
640
            )
641
        elif options.app == "firefox-snap" and options.allow_sudo is False:
9✔
642
            self.logger.warning(
9✔
643
                "Bisection on Snap package without --allow-sudo, you will be prompted for"
644
                " credential on each 'snap' command."
645
            )
646
        elif options.allow_sudo is True and options.app != "firefox-snap":
9✔
NEW
647
            raise MozRegressionError(
×
648
                f"--allow-sudo specified for app ({options.app}), but only valid for "
649
                f"firefox-snap. Please verify your config."
650
            )
651
        elif options.disable_snap_connect is True and options.app != "firefox-snap":
9✔
NEW
652
            raise MozRegressionError(
×
653
                f"--disable-snap-conncet specified for app ({options.app}), but only valid for "
654
                f"firefox-snap. Please verify your config."
655
            )
656

657
        fetch_config = create_config(
9✔
658
            options.app, mozinfo.os, options.bits, mozinfo.processor, options.arch
659
        )
660
        if options.lang:
9✔
661
            if options.app not in ("firefox-l10n", "thunderbird-l10n"):
×
662
                raise MozRegressionError(
×
663
                    "--lang is only valid with --app=firefox-l10n|thunderbird-l10n"
664
                )
665
            fetch_config.set_lang(options.lang)
×
666
        elif options.app in ("firefox-l10n", "thunderbird-l10n"):
9✔
667
            raise MozRegressionError(f"app {options.app} requires a --lang argument")
×
668
        if options.build_type:
9✔
669
            try:
9✔
670
                fetch_config.set_build_type(options.build_type)
9✔
671
            except MozRegressionError as msg:
9✔
672
                self.logger.warning("%s (Defaulting to %r)" % (msg, fetch_config.build_type))
9✔
673
        self.fetch_config = fetch_config
9✔
674

675
        fetch_config.set_repo(options.repo)
9✔
676
        fetch_config.set_base_url(options.archive_base_url)
9✔
677

678
        if (
9✔
679
            not user_defined_bits
680
            and options.bits == 64
681
            and mozinfo.os == "win"
682
            and 32 in fetch_config.available_bits()
683
        ):
684
            # inform users on windows that we are using 64 bit builds.
685
            self.logger.info("bits option not specified, using 64-bit builds.")
×
686

687
        if options.bits == 32 and mozinfo.os == "mac":
9✔
688
            self.logger.info("only 64-bit builds available for mac, using 64-bit builds.")
×
689

690
        if fetch_config.is_integration() and fetch_config.tk_needs_auth():
9✔
691
            creds = tc_authenticate(self.logger)
×
692
            fetch_config.set_tk_credentials(creds)
×
693

694
        # set action for just use changset or data to bisect
695
        if options.launch:
9✔
696
            options.launch = self._convert_to_bisect_arg(options.launch)
9✔
697
            self.action = "launch_integration"
9✔
698
            if is_date_or_datetime(options.launch) and fetch_config.should_use_archive():
9✔
699
                self.action = "launch_nightlies"
9✔
700
        else:
701
            # define good/bad default values if required
702
            default_good_date, default_bad_date = get_default_date_range(fetch_config)
9✔
703
            if options.find_fix:
9✔
704
                default_bad_date, default_good_date = (
9✔
705
                    default_good_date,
706
                    default_bad_date,
707
                )
708
            if not options.bad:
9✔
709
                options.bad = default_bad_date
9✔
710
                self.logger.info("No 'bad' option specified, using %s" % options.bad)
9✔
711
            else:
712
                options.bad = self._convert_to_bisect_arg(options.bad)
9✔
713
            if not options.good:
9✔
714
                options.good = default_good_date
9✔
715
                self.logger.info("No 'good' option specified, using %s" % options.good)
9✔
716
            else:
717
                options.good = self._convert_to_bisect_arg(options.good)
9✔
718

719
            self.action = "bisect_integration"
9✔
720
            if is_date_or_datetime(options.good) and is_date_or_datetime(options.bad):
9✔
721
                if not options.find_fix and to_datetime(options.good) > to_datetime(options.bad):
9✔
722
                    raise MozRegressionError(
9✔
723
                        (
724
                            "Good date %s is later than bad date %s."
725
                            " Maybe you wanted to use the --find-fix"
726
                            " flag?"
727
                        )
728
                        % (options.good, options.bad)
729
                    )
730
                elif options.find_fix and to_datetime(options.good) < to_datetime(options.bad):
9✔
731
                    raise MozRegressionError(
9✔
732
                        (
733
                            "Bad date %s is later than good date %s."
734
                            " You should not use the --find-fix flag"
735
                            " in this case..."
736
                        )
737
                        % (options.bad, options.good)
738
                    )
739
                if fetch_config.should_use_archive():
9✔
740
                    self.action = "bisect_nightlies"
9✔
741
        if (
9✔
742
            self.action in ("launch_integration", "bisect_integration")
743
            and not fetch_config.is_integration()
744
        ):
745
            raise MozRegressionError(
×
746
                "Unable to bisect integration for `%s`" % fetch_config.app_name
747
            )
748
        options.preferences = preferences(options.prefs_files, options.prefs, self.logger)
9✔
749
        # convert GiB to bytes.
750
        options.persist_size_limit = int(abs(float(options.persist_size_limit)) * 1073741824)
9✔
751

752

753
def cli(argv=None, conf_file=DEFAULT_CONF_FNAME, namespace=None):
9✔
754
    """
755
    parse cli args basically and returns a :class:`Configuration`.
756

757
    if namespace is given, it will be used as a arg parsing result, so no
758
    arg parsing will be done.
759
    """
760
    config = get_config(conf_file)
9✔
761
    if namespace:
9✔
762
        options = namespace
×
763
    else:
764
        options = parse_args(argv=argv, defaults=config)
9✔
765
        if not options.cmdargs:
9✔
766
            # we don't set the cmdargs default to be that from the
767
            # configuration file, because then any new arguments
768
            # will be appended: https://bugs.python.org/issue16399
769
            options.cmdargs = config["cmdargs"]
9✔
770
    if conf_file and not os.path.isfile(conf_file):
9✔
771
        print("*" * 10)
9✔
772
        print(
9✔
773
            colorize(
774
                "You should use a config file. Please use the "
775
                + "{sBRIGHT}--write-config{sRESET_ALL}"
776
                + " command line flag to help you create one."
777
            )
778
        )
779
        print("*" * 10)
9✔
780
        print()
9✔
781
    return Configuration(options, config)
9✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc