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

mozilla / mozregression / 14310821897

07 Apr 2025 01:43PM CUT coverage: 35.021%. First build
14310821897

Pull #1960

github

web-flow
Merge 8d54d20c5 into 807564865
Pull Request #1960: build(deps): bump yarl from 1.9.4 to 1.19.0

989 of 2824 relevant lines covered (35.02%)

0.35 hits per line

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

0.0
/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
×
10

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

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

20
from mozregression import __version__
×
21
from mozregression.branches import get_name
×
22
from mozregression.config import DEFAULT_CONF_FNAME, get_config, write_config
×
23
from mozregression.dates import is_date_or_datetime, parse_date, to_datetime
×
24
from mozregression.errors import DateFormatError, MozRegressionError, UnavailableRelease
×
25
from mozregression.fetch_configs import REGISTRY as FC_REGISTRY
×
26
from mozregression.fetch_configs import create_config
×
27
from mozregression.log import colorize, init_logger
×
28
from mozregression.releases import (
×
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
×
35

36

37
class _StopAction(Action):
×
38
    def __init__(self, option_strings, dest=SUPPRESS, default=SUPPRESS, help=None):
×
39
        super(_StopAction, self).__init__(
×
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):
×
48
        raise NotImplementedError
49

50

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

56

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

62

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

72

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

80

81
def create_parser(defaults):
×
82
    """
83
    Create the mozregression command line parser (ArgumentParser instance).
84
    """
85
    usage = (
×
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)
×
101
    parser.add_argument(
×
102
        "--version",
103
        action="version",
104
        version=__version__,
105
        help=("print the mozregression version number and" " exits."),
106
    )
107

108
    parser.add_argument(
×
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(
×
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(
×
127
        "--list-releases",
128
        action=ListReleasesAction,
129
        help="list all known releases and exit",
130
    )
131

132
    parser.add_argument(
×
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(
×
145
        "--list-build-types",
146
        action=ListBuildTypesAction,
147
        help="List available build types combinations.",
148
    )
149

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

156
    parser.add_argument(
×
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(
×
167
        "-p",
168
        "--profile",
169
        default=defaults["profile"],
170
        metavar="PATH",
171
        help="profile to use with nightlies.",
172
    )
173

174
    parser.add_argument(
×
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(
×
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(
×
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(
×
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(
×
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(
×
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(
×
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(
×
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(
×
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(
×
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(
×
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(
×
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(
×
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(
×
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(
×
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(
×
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(
×
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(
×
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(
×
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(
×
415
        "--write-config",
416
        action=WriteConfigAction,
417
        help="Helps to write the configuration file.",
418
    )
419

420
    parser.add_argument("--debug", "-d", action="store_true", help="Show the debug output.")
×
421

422
    return parser
×
423

424

425
def parse_bits(option_bits):
×
426
    """
427
    Returns the correctly typed bits.
428
    """
429
    if option_bits == "32":
×
430
        return 32
×
431
    else:
432
        # if 64 bits is passed on a 32 bit system, it won't be honored
433
        return mozinfo.bits
×
434

435

436
def preferences(prefs_files, prefs_args, logger):
×
437
    """
438
    profile preferences
439
    """
440
    # object that will hold the preferences
441
    prefs = mozprofile.prefs.Preferences()
×
442

443
    # add preferences files
444
    if prefs_files:
×
445
        for prefs_file in prefs_files:
×
446
            prefs.add_file(prefs_file)
×
447

448
    separator = ":"
×
449
    cli_prefs = []
×
450
    if prefs_args:
×
451
        for pref in prefs_args:
×
452
            if separator not in pref:
×
453
                if logger:
×
454
                    if "=" in pref:
×
455
                        logger.warning('Pref %s has an "=", did you mean to use ":"?' % pref)
×
456
                    logger.info('Dropping pref %s for missing separator ":"' % pref)
×
457
                continue
×
458
            cli_prefs.append(pref.split(separator, 1))
×
459

460
    # string preferences
461
    prefs.add(cli_prefs, cast=True)
×
462

463
    return prefs()
×
464

465

466
def get_default_date_range(fetch_config):
×
467
    """
468
    Compute the default date range (first, last) to bisect.
469
    """
470
    last_date = datetime.date.today()
×
471
    first_date = datetime.date.today() - datetime.timedelta(days=365)
×
472

473
    return first_date, last_date
×
474

475

476
class Configuration(object):
×
477
    """
478
    Holds the configuration extracted from the command line + configuration file.
479

480
    This is usually instantiated by calling :func:`cli`.
481

482
    The constructor only initializes the `logger`.
483

484
    The configuration should not be used (except for the logger attribute)
485
    until :meth:`validate` is called.
486

487
    :attr logger: the mozlog logger, created using the command line options
488
    :attr options: the raw command line options
489
    :attr action: the action that the user want to do. This is a string
490
                  ("bisect_integration" or "bisect_nightlies")
491
    :attr fetch_config: the fetch_config instance, required to find
492
                        information about a build
493
    """
494

495
    def __init__(self, options, config):
×
496
        self.options = options
×
497
        self.logger = init_logger(debug=options.debug)
×
498
        # allow to filter process output based on the user option
499
        if options.process_output is None:
×
500
            # process_output not user defined
501
            log_process_output = options.build_type != ""
×
502
        else:
503
            log_process_output = options.process_output == "stdout"
×
504
        get_default_logger("process").component_filter = lambda data: (
×
505
            data if log_process_output else None
506
        )
507

508
        # filter some mozversion log lines
509
        re_ignore_mozversion_line = re.compile(
×
510
            r"^(platform_.+|application_vendor|application_remotingname"
511
            r"|application_id|application_display_name): .+"
512
        )
513
        get_default_logger("mozversion").component_filter = lambda data: (
×
514
            None if re_ignore_mozversion_line.match(data["message"]) else data
515
        )
516

517
        self.enable_telemetry = config["enable-telemetry"] not in ("no", "false", 0)
×
518

519
        self.action = None
×
520
        self.fetch_config = None
×
521

522
    def _convert_to_bisect_arg(self, value):
×
523
        """
524
        Transform a string value to a date or datetime if it looks like it.
525
        """
526
        try:
×
527
            value = parse_date(value)
×
528
        except DateFormatError:
×
529
            try:
×
530
                repo = self.options.repo
×
531
                if get_name(repo) == "mozilla-release" or (
×
532
                    not repo and re.match(r"^\d+\.\d(\.\d)?$", value)
533
                ):
534
                    new_value = tag_of_release(value)
×
535
                    if not repo:
×
536
                        self.logger.info("Assuming repo mozilla-release")
×
537
                        self.fetch_config.set_repo("mozilla-release")
×
538
                    self.logger.info("Using tag %s for release %s" % (new_value, value))
×
539
                    value = new_value
×
540
                elif get_name(repo) == "mozilla-beta" or (
×
541
                    not repo and re.match(r"^\d+\.0b\d+$", value)
542
                ):
543
                    new_value = tag_of_beta(value)
×
544
                    if not repo:
×
545
                        self.logger.info("Assuming repo mozilla-beta")
×
546
                        self.fetch_config.set_repo("mozilla-beta")
×
547
                    self.logger.info("Using tag %s for release %s" % (new_value, value))
×
548
                    value = new_value
×
549
                else:
550
                    new_value = parse_date(date_of_release(value))
×
551
                    self.logger.info("Using date %s for release %s" % (new_value, value))
×
552
                    value = new_value
×
553
            except UnavailableRelease:
×
554
                self.logger.info("%s is not a release, assuming it's a hash..." % value)
×
555
        return value
×
556

557
    def validate(self):
×
558
        """
559
        Validate the options, define the `action` and `fetch_config` that
560
        should be used to run the application.
561
        """
562
        options = self.options
×
563

564
        arch_options = {
×
565
            "firefox": [
566
                "aarch64",
567
                "x86",
568
                "x86_64",
569
            ],
570
            "firefox-l10n": [
571
                "aarch64",
572
                "x86",
573
                "x86_64",
574
            ],
575
            "gve": [
576
                "aarch64",
577
                "arm",
578
                "x86_64",
579
            ],
580
            "fenix": [
581
                "arm64-v8a",
582
                "armeabi-v7a",
583
                "x86",
584
                "x86_64",
585
            ],
586
            "focus": [
587
                "arm64-v8a",
588
                "armeabi-v7a",
589
                "x86",
590
                "x86_64",
591
            ],
592
        }
593

594
        user_defined_bits = options.bits is not None
×
595
        options.bits = parse_bits(options.bits or mozinfo.bits)
×
596

597
        if options.arch is not None:
×
598
            if user_defined_bits:
×
599
                self.logger.warning(
600
                    "--arch and --bits are passed together. --arch will be preferred."
601
                )
602

603
            if options.app not in arch_options:
×
604
                self.logger.warning(f"--arch ignored for {options.app}.")
×
605
                options.arch = None
×
606
            elif options.app in ("firefox", "firefox-l10n") and mozinfo.os == "mac":
×
607
                self.logger.warning(
×
608
                    "--arch ignored for Firefox for macOS as it uses unified binary."
609
                )
610
                options.arch = None
×
611
            elif options.arch not in arch_options[options.app]:
×
612
                raise MozRegressionError(
×
613
                    f"Invalid arch ({options.arch}) specified for app ({options.app}). "
614
                    f"Valid options are: {', '.join(arch_options[options.app])}."
615
                )
616
        elif options.app in ("fenix", "focus"):
×
617
            raise MozRegressionError(
×
618
                f"`--arch` required for specified app ({options.app}). "
619
                f"Please specify one of {', '.join(arch_options[options.app])}."
620
            )
621

622
        fetch_config = create_config(
×
623
            options.app, mozinfo.os, options.bits, mozinfo.processor, options.arch
624
        )
625
        if options.lang:
×
626
            if options.app not in ("firefox-l10n", "thunderbird-l10n"):
×
627
                raise MozRegressionError(
×
628
                    "--lang is only valid with --app=firefox-l10n|thunderbird-l10n"
629
                )
630
            fetch_config.set_lang(options.lang)
×
631
        elif options.app in ("firefox-l10n", "thunderbird-l10n"):
×
632
            raise MozRegressionError(f"app {options.app} requires a --lang argument")
×
633
        if options.build_type:
×
634
            try:
×
635
                fetch_config.set_build_type(options.build_type)
×
636
            except MozRegressionError as msg:
×
637
                self.logger.warning("%s (Defaulting to %r)" % (msg, fetch_config.build_type))
×
638
        self.fetch_config = fetch_config
×
639

640
        fetch_config.set_repo(options.repo)
×
641
        fetch_config.set_base_url(options.archive_base_url)
×
642

643
        if (
×
644
            not user_defined_bits
645
            and options.bits == 64
646
            and mozinfo.os == "win"
647
            and 32 in fetch_config.available_bits()
648
        ):
649
            # inform users on windows that we are using 64 bit builds.
650
            self.logger.info("bits option not specified, using 64-bit builds.")
×
651

652
        if options.bits == 32 and mozinfo.os == "mac":
×
653
            self.logger.info("only 64-bit builds available for mac, using 64-bit builds.")
×
654

655
        if fetch_config.is_integration() and fetch_config.tk_needs_auth():
×
656
            creds = tc_authenticate(self.logger)
×
657
            fetch_config.set_tk_credentials(creds)
×
658

659
        # set action for just use changset or data to bisect
660
        if options.launch:
×
661
            options.launch = self._convert_to_bisect_arg(options.launch)
×
662
            self.action = "launch_integration"
×
663
            if is_date_or_datetime(options.launch) and fetch_config.should_use_archive():
×
664
                self.action = "launch_nightlies"
×
665
        else:
666
            # define good/bad default values if required
667
            default_good_date, default_bad_date = get_default_date_range(fetch_config)
×
668
            if options.find_fix:
×
669
                default_bad_date, default_good_date = (
×
670
                    default_good_date,
671
                    default_bad_date,
672
                )
673
            if not options.bad:
×
674
                options.bad = default_bad_date
×
675
                self.logger.info("No 'bad' option specified, using %s" % options.bad)
×
676
            else:
677
                options.bad = self._convert_to_bisect_arg(options.bad)
×
678
            if not options.good:
×
679
                options.good = default_good_date
×
680
                self.logger.info("No 'good' option specified, using %s" % options.good)
×
681
            else:
682
                options.good = self._convert_to_bisect_arg(options.good)
×
683

684
            self.action = "bisect_integration"
×
685
            if is_date_or_datetime(options.good) and is_date_or_datetime(options.bad):
×
686
                if not options.find_fix and to_datetime(options.good) > to_datetime(options.bad):
×
687
                    raise MozRegressionError(
×
688
                        (
689
                            "Good date %s is later than bad date %s."
690
                            " Maybe you wanted to use the --find-fix"
691
                            " flag?"
692
                        )
693
                        % (options.good, options.bad)
694
                    )
695
                elif options.find_fix and to_datetime(options.good) < to_datetime(options.bad):
×
696
                    raise MozRegressionError(
×
697
                        (
698
                            "Bad date %s is later than good date %s."
699
                            " You should not use the --find-fix flag"
700
                            " in this case..."
701
                        )
702
                        % (options.bad, options.good)
703
                    )
704
                if fetch_config.should_use_archive():
×
705
                    self.action = "bisect_nightlies"
×
706
        if (
×
707
            self.action in ("launch_integration", "bisect_integration")
708
            and not fetch_config.is_integration()
709
        ):
710
            raise MozRegressionError(
×
711
                "Unable to bisect integration for `%s`" % fetch_config.app_name
712
            )
713
        options.preferences = preferences(options.prefs_files, options.prefs, self.logger)
×
714
        # convert GiB to bytes.
715
        options.persist_size_limit = int(abs(float(options.persist_size_limit)) * 1073741824)
×
716

717

718
def cli(argv=None, conf_file=DEFAULT_CONF_FNAME, namespace=None):
×
719
    """
720
    parse cli args basically and returns a :class:`Configuration`.
721

722
    if namespace is given, it will be used as a arg parsing result, so no
723
    arg parsing will be done.
724
    """
725
    config = get_config(conf_file)
×
726
    if namespace:
×
727
        options = namespace
×
728
    else:
729
        options = parse_args(argv=argv, defaults=config)
×
730
        if not options.cmdargs:
×
731
            # we don't set the cmdargs default to be that from the
732
            # configuration file, because then any new arguments
733
            # will be appended: https://bugs.python.org/issue16399
734
            options.cmdargs = config["cmdargs"]
×
735
    if conf_file and not os.path.isfile(conf_file):
×
736
        print("*" * 10)
×
737
        print(
×
738
            colorize(
739
                "You should use a config file. Please use the "
740
                + "{sBRIGHT}--write-config{sRESET_ALL}"
741
                + " command line flag to help you create one."
742
            )
743
        )
744
        print("*" * 10)
×
745
        print()
×
746
    return Configuration(options, config)
×
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