Example #1
0
 def start(self):
     profile = get_module_profile(
         get_module("fontbakery.profiles." + self.profilename))
     print(self.checks)
     runner = CheckRunner(profile,
                          values={"fonts": self.paths},
                          config={
                              "custom_order": None,
                              "explicit_checks": self.checks,
                              "exclude_checks": None
                          })
     print("Log levels: ", self.loglevels)
     hr = HTMLReporter(runner=runner, loglevels=self.loglevels)
     ghmd = GHMarkdownReporter(runner=runner, loglevels=self.loglevels)
     prog = ProgressReporter(self.progressStatus, runner=runner)
     reporters = [hr.receive, prog.receive, ghmd.receive]
     status_generator = runner.run()
     print("Starting distribute_generator")
     distribute_generator(status_generator, reporters)
     print("Done with distribute_generator")
     self.signalStatus.emit(hr.get_html(), ghmd.get_markdown())
def doit(args):

    logger = args.logger
    htmlfile = args.html
    if args.ttfaudit:  # Special action to compare checks in profile against check_list values
        audit(args.fonts,
              logger)  # args.fonts used as output file name for audit
        return

    # Process list of fonts supplied, expanding wildcards using glob if needed
    fonts = []
    fontstype = None
    for pattern in args.fonts:
        for fullpath in glob.glob(pattern):
            ftype = fullpath.lower().rsplit(".", 1)[-1]
            if ftype == "otf": ftype = "ttf"
            if ftype not in ("ttf", "ufo"):
                logger.log(
                    "Fonts must be OpenType or UFO - " + fullpath + " invalid",
                    "S")
            if fontstype is None:
                fontstype = ftype
            else:
                if ftype != fontstype:
                    logger.log(
                        "All fonts must be of the same type - both UFO and ttf/otf fonts supplied",
                        "S")
            fonts.append(fullpath)

    if fonts == []:
        logger.log(
            "No files match the filespec provided for fonts: " +
            str(args.fonts), "S")

    # Create the profile object
    if args.profile:
        proname = args.profile
    else:
        if fontstype == "ttf":
            proname = "silfont.fbtests.ttfchecks"
        else:
            logger.log("UFO fonts not yet supported", "S")

    try:
        module = get_module(proname)
    except Exception as e:
        logger.log("Failed to import profile: " + proname + "\n" + str(e), "S")

    profile = get_module_profile(module)
    psfcheck_list = module.psfcheck_list

    # Create the runner and reporter objetcs, then run the tests
    runner = CheckRunner(profile, values={"fonts": fonts})

    sr = SerializeReporter(
        runner=runner
    )  # This produces results from all the tests in sr.getdoc for later analysis
    reporters = [sr.receive]

    if htmlfile:
        hr = HTMLReporter(runner=runner, loglevels=[SKIP])
        reporters.append(hr.receive)

    distribute_generator(runner.run(), reporters)

    # Process the results
    results = sr.getdoc()
    sections = results["sections"]

    checks = {}
    maxname = 11
    somedebug = False
    overrides = {}
    tempoverrides = False

    for section in sections:
        secchecks = section["checks"]
        for check in secchecks:
            checkid = check["key"][1][17:-1]
            fontfile = check[
                "filename"] if "filename" in check else "Family-wide"
            path, fontname = os.path.split(fontfile)
            if fontname not in checks:
                checks[fontname] = {
                    "ERROR": [],
                    "FAIL": [],
                    "WARN": [],
                    "INFO": [],
                    "SKIP": [],
                    "PASS": [],
                    "DEBUG": []
                }
                if len(fontname) > maxname: maxname = len(fontname)
            status = check["logs"][0]["status"]
            if checkid in psfcheck_list:
                # Look for status overrides
                (changetype, temp) = ("temp_change_status", True) if "temp_change_status" in psfcheck_list[checkid]\
                    else ("change_status", False)
                if changetype in psfcheck_list[checkid]:
                    change_status = psfcheck_list[checkid][changetype]
                    if status in change_status:
                        reason = change_status[
                            "reason"] if "reason" in change_status else None
                        overrides[fontname + ", " +
                                  checkid] = (status + " to " +
                                              change_status[status], temp,
                                              reason)
                        if temp: tempoverrides = True
                        status = change_status[status]
            checks[fontname][status].append(check)
            if status == "DEBUG": somedebug = True

    if htmlfile:
        logger.log("Writing results to " + htmlfile, "P")
        with open(htmlfile, 'w') as hfile:
            hfile.write(hr.get_html())

    fbstats = ["ERROR", "FAIL", "WARN", "INFO", "SKIP", "PASS"]
    psflevels = ["E", "E", "W", "I", "I", "V"]
    if somedebug:  # Only have debug column if some debug statuses are present
        fbstats.append("DEBUG")
        psflevels.append("W")
    wrapper = TextWrapper(width=120,
                          initial_indent="   ",
                          subsequent_indent="   ")
    errorcnt = 0
    failcnt = 0
    summarymess = "Check status summary:\n"
    summarymess += "{:{pad}}ERROR  FAIL  WARN  INFO  SKIP  PASS".format(
        "", pad=maxname + 4)
    if somedebug: summarymess += "  DEBUG"
    fontlist = list(sorted(x for x in checks if x != "Family-wide")) + [
        "Family-wide"
    ]  # Sort with Family-wide last
    for fontname in fontlist:
        summarymess += "\n  {:{pad}}".format(fontname, pad=maxname)
        for i, status in enumerate(fbstats):
            psflevel = psflevels[i]
            checklist = checks[fontname][status]
            cnt = len(checklist)
            if cnt > 0 or status != "DEBUG":
                summarymess += "{:6d}".format(cnt)  # Suppress 0 for DEBUG
            if cnt:
                if status == "ERROR": errorcnt += cnt
                if status == "FAIL": failcnt += cnt
                messparts = [
                    "Checks with status {} for {}".format(status, fontname)
                ]
                for check in checklist:
                    messparts.append(" > {}".format(check["key"][1][17:-1]))
                    messparts += wrapper.wrap(check["logs"][0]["message"])
                logger.log("\n".join(messparts), psflevel)
    if overrides != {}:
        summarymess += "\n  Note: " + str(
            len(overrides)
        ) + " Fontbakery statuses were overridden - see log file for details"
        if tempoverrides:
            summarymess += "\n        ******** Some of the overrides were temporary overrides ********"
    logger.log(summarymess, "P")

    if overrides != {}:
        for oname in overrides:
            override = overrides[oname]
            mess = "Status override for " + oname + ": " + override[0]
            if override[1]: mess += " (Temporary override)"
            logger.log(mess, "W")
            if override[2] is not None:
                logger.log("Override reason: " + override[2], "I")

    if errorcnt + failcnt > 0:
        mess = str(
            failcnt) + " test(s) gave a status of FAIL" if failcnt > 0 else ""
        if errorcnt > 0:
            if failcnt > 0: mess += "\n                              "
            mess += str(errorcnt) + " test(s) gave a status of ERROR which means they failed to execute properly." \
                                    "\n                              " \
                                    "   ERROR probably indicates a software issue rather than font issue"
        logger.log(mess, "S")
Example #3
0
def main(profile=None, values=None):
    # profile can be injected by e.g. check-googlefonts injects it's own profile
    add_profile_arg = False
    if profile is None:
        profile = get_profile()
        add_profile_arg = True

    argument_parser, values_keys = ArgumentParser(profile,
                                                  profile_arg=add_profile_arg)
    args = argument_parser.parse_args()

    if args.list_checks:
        if args.loglevels == [PASS]:  # if verbose:
            from fontbakery.constants import WHITE_STR, CYAN_STR, BLUE_STR
            for section in profile._sections.values():
                print(WHITE_STR.format("\nSection:") + " " + section.name)
                for check in section._checks:
                    print(
                        CYAN_STR.format(check.id) + "\n" +
                        BLUE_STR.format(f'"{check.description}"') + "\n")
        else:
            for section_name, section in profile._sections.items():
                for check in section._checks:
                    print(check.id)
        sys.exit()

    values_ = {}
    if values is not None:
        values_.update(values)

    # values_keys are returned by profile.setup_argparse
    # these are keys for custom arguments required by the profile.
    if values_keys:
        for key in values_keys:
            if hasattr(args, key):
                values_[key] = getattr(args, key)

    try:
        runner = CheckRunner(profile,
                             values=values_,
                             custom_order=args.order,
                             explicit_checks=args.checkid,
                             exclude_checks=args.exclude_checkid)
    except ValueValidationError as e:
        print(e)
        argument_parser.print_usage()
        sys.exit(1)

    # The default Windows Terminal just displays the escape codes. The argument
    # parser above therefore has these options disabled.
    if sys.platform == "win32":
        args.no_progress = True
        args.no_colors = True

    # the most verbose loglevel wins
    loglevel = min(args.loglevels) if args.loglevels else DEFAULT_LOG_LEVEL
    tr = TerminalReporter(runner=runner, is_async=False
                         , print_progress=not args.no_progress
                         , check_threshold=loglevel
                         , log_threshold=args.loglevel_messages or loglevel
                         , usecolor=not args.no_colors
                         , collect_results_by=args.gather_by
                         , skip_status_report=None if args.show_sections\
                                                        else (STARTSECTION, ENDSECTION)

                         )
    reporters = [tr.receive]

    if args.json:
        sr = SerializeReporter(runner=runner,
                               collect_results_by=args.gather_by)
        reporters.append(sr.receive)

    if args.ghmarkdown:
        mdr = GHMarkdownReporter(loglevels=args.loglevels,
                                 runner=runner,
                                 collect_results_by=args.gather_by)
        reporters.append(mdr.receive)

    if args.html:
        hr = HTMLReporter(loglevels=args.loglevels,
                          runner=runner,
                          collect_results_by=args.gather_by)
        reporters.append(hr.receive)

    distribute_generator(runner.run(), reporters)

    if args.json:
        import json
        json.dump(sr.getdoc(), args.json, sort_keys=True, indent=4)
        print("A report in JSON format has been"
              " saved to '{}'".format(args.json.name))

    if args.ghmarkdown:
        args.ghmarkdown.write(mdr.get_markdown())
        print("A report in GitHub Markdown format which can be useful\n"
              " for posting issues on a GitHub issue tracker has been\n"
              " saved to '{}'".format(args.ghmarkdown.name))

    if args.html:
        args.html.write(hr.get_html())
        print(f"A report in HTML format has been saved to '{args.html.name}'")

    # Fail and error let the command fail
    return 1 if tr.worst_check_status in (ERROR, FAIL) else 0
Example #4
0
def main(profile=None, values=None):
  # profile can be injected by e.g. check-googlefonts injects it's own profile
  add_profile_arg = False
  if profile is None:
    profile = get_profile()
    add_profile_arg = True

  argument_parser, values_keys = ArgumentParser(profile, profile_arg=add_profile_arg)
  args = argument_parser.parse_args()

  if args.list_checks:
    if args.loglevels == [PASS]: # if verbose:
      from fontbakery.constants import WHITE_STR, CYAN_STR, BLUE_STR
      for section in profile._sections.values():
        print(WHITE_STR.format("\nSection:") + " " + section.name)
        for check in section._checks:
          print(CYAN_STR.format(check.id) + "\n" +
                BLUE_STR.format(f'"{check.description}"') + "\n")
    else:
      for section_name, section in profile._sections.items():
        for check in section._checks:
          print(check.id)
    sys.exit()

  values_ = {}
  if values is not None:
    values_.update(values)

  # values_keys are returned by profile.setup_argparse
  # these are keys for custom arguments required by the profile.
  if values_keys:
    for key in values_keys:
      if hasattr(args, key):
        values_[key] = getattr(args, key)

  try:
    runner = CheckRunner(profile
                        , values=values_
                        , custom_order=args.order
                        , explicit_checks=args.checkid
                        , exclude_checks=args.exclude_checkid
                        )
  except ValueValidationError as e:
    print(e)
    argument_parser.print_usage()
    sys.exit(1)

  # The default Windows Terminal just displays the escape codes. The argument
  # parser above therefore has these options disabled.
  if sys.platform == "win32":
    args.no_progress = True
    args.no_colors = True

  # the most verbose loglevel wins
  loglevel = min(args.loglevels) if args.loglevels else DEFAULT_LOG_LEVEL
  tr = TerminalReporter(runner=runner, is_async=False
                       , print_progress=not args.no_progress
                       , check_threshold=loglevel
                       , log_threshold=args.loglevel_messages or loglevel
                       , usecolor=not args.no_colors
                       , collect_results_by=args.gather_by
                       , skip_status_report=None if args.show_sections\
                                                      else (STARTSECTION, ENDSECTION)

                       )
  reporters = [tr.receive]

  if args.json:
    sr = SerializeReporter(runner=runner, collect_results_by=args.gather_by)
    reporters.append(sr.receive)

  if args.ghmarkdown:
    mdr = GHMarkdownReporter(loglevels=args.loglevels,
                             runner=runner,
                             collect_results_by=args.gather_by)
    reporters.append(mdr.receive)

  if args.html:
    hr = HTMLReporter(loglevels=args.loglevels,
                      runner=runner,
                      collect_results_by=args.gather_by)
    reporters.append(hr.receive)

  distribute_generator(runner.run(), reporters)

  if args.json:
    import json
    json.dump(sr.getdoc(), args.json, sort_keys=True, indent=4)
    print("A report in JSON format has been"
          " saved to '{}'".format(args.json.name))

  if args.ghmarkdown:
    args.ghmarkdown.write(mdr.get_markdown())
    print("A report in GitHub Markdown format which can be useful\n"
          " for posting issues on a GitHub issue tracker has been\n"
          " saved to '{}'".format(args.ghmarkdown.name))

  if args.html:
    args.html.write(hr.get_html())
    print(f"A report in HTML format has been saved to '{args.html.name}'")

  # Fail and error let the command fail
  return 1 if tr.worst_check_status in (ERROR, FAIL) else 0
Example #5
0
def main(profile=None, values=None):
    # profile can be injected by e.g. check-googlefonts injects it's own profile
    add_profile_arg = False
    if profile is None:
        profile = get_profile()
        add_profile_arg = True

    argument_parser, values_keys = ArgumentParser(profile,
                                                  profile_arg=add_profile_arg)
    args = argument_parser.parse_args()

    # The default Windows Terminal just displays the escape codes. The argument
    # parser above therefore has these options disabled.
    if sys.platform == "win32":
        args.no_progress = True
        args.no_colors = True

    theme = get_theme(args)
    # the most verbose loglevel wins
    loglevel = min(args.loglevels) if args.loglevels else DEFAULT_LOG_LEVEL

    if args.list_checks:
        list_checks(profile, theme, verbose=loglevel > DEFAULT_LOG_LEVEL)

    values_ = {}
    if values is not None:
        values_.update(values)

    # values_keys are returned by profile.setup_argparse
    # these are keys for custom arguments required by the profile.
    if values_keys:
        for key in values_keys:
            if hasattr(args, key):
                values_[key] = getattr(args, key)

    if args.configfile:
        configuration = Configuration.from_config_file(args.configfile)
    else:
        configuration = Configuration()

    # Command line args overrides config, but only if given
    configuration.maybe_override(
        Configuration(custom_order=args.order,
                      explicit_checks=args.checkid,
                      exclude_checks=args.exclude_checkid))
    runner_kwds = dict(values=values_, config=configuration)
    try:
        runner = CheckRunner(profile, **runner_kwds)
    except ValueValidationError as e:
        print(e)
        argument_parser.print_usage()
        sys.exit(1)

    is_async = args.multiprocessing != 0

    tr = TerminalReporter(runner=runner, is_async=is_async
                         , print_progress=not args.no_progress
                         , succinct=args.succinct
                         , check_threshold=loglevel
                         , log_threshold=args.loglevel_messages or loglevel
                         , theme=theme
                         , collect_results_by=args.gather_by
                         , skip_status_report=None if args.show_sections \
                                                   else (SECTIONSUMMARY, )
                         )
    reporters = [tr]
    if "reporters" not in args:
        args.reporters = []

    for reporter_class, output_file in args.reporters:
        reporters.append(
            reporter_class(loglevels=args.loglevels,
                           runner=runner,
                           collect_results_by=args.gather_by,
                           output_file=output_file))

    if args.multiprocessing == 0:
        status_generator = runner.run()
    else:
        status_generator = multiprocessing_runner(args.multiprocessing, runner,
                                                  runner_kwds)

    distribute_generator(status_generator,
                         [reporter.receive for reporter in reporters])

    for reporter in reporters:
        reporter.write()

    # Fail and error let the command fail
    return 1 if tr.worst_check_status in (ERROR, FAIL) else 0
Example #6
0
def main(profile=None, values=None):
    # profile can be injected by e.g. check-googlefonts injects it's own profile
    add_profile_arg = False
    if profile is None:
        profile = get_profile()
        add_profile_arg = True

    argument_parser, values_keys = ArgumentParser(profile,
                                                  profile_arg=add_profile_arg)
    args = argument_parser.parse_args()

    # The default Windows Terminal just displays the escape codes. The argument
    # parser above therefore has these options disabled.
    if sys.platform == "win32":
        args.no_progress = True
        args.no_colors = True

    from fontbakery.constants import NO_COLORS_THEME, DARK_THEME, LIGHT_THEME
    if args.no_colors:
        theme = NO_COLORS_THEME
    else:
        if args.light_theme:
            theme = LIGHT_THEME
        elif args.dark_theme:
            theme = DARK_THEME
        elif sys.platform == "darwin":
            # The vast majority of MacOS users seem to use a light-background on the text terminal
            theme = LIGHT_THEME
        else:
            # For orther systems like GNU+Linux and Windows, a dark terminal seems to be more common.
            theme = DARK_THEME

    if args.list_checks:
        if args.loglevels == [PASS]:  # if verbose:
            for section in profile._sections.values():
                print(theme["list-checks: section"]("\nSection:") + " " +
                      section.name)
                for check in section._checks:
                    print(theme["list-checks: check-id"](check.id) + "\n" +
                          theme["list-checks: description"]
                          (f'"{check.description}"') + "\n")
        else:
            for section_name, section in profile._sections.items():
                for check in section._checks:
                    print(check.id)
        sys.exit()

    values_ = {}
    if values is not None:
        values_.update(values)

    # values_keys are returned by profile.setup_argparse
    # these are keys for custom arguments required by the profile.
    if values_keys:
        for key in values_keys:
            if hasattr(args, key):
                values_[key] = getattr(args, key)

    runner_kwds = dict(values=values_,
                       custom_order=args.order,
                       explicit_checks=args.checkid,
                       exclude_checks=args.exclude_checkid)
    try:
        runner = CheckRunner(profile, **runner_kwds)
    except ValueValidationError as e:
        print(e)
        argument_parser.print_usage()
        sys.exit(1)

    is_async = args.multiprocessing != 0

    # the most verbose loglevel wins
    loglevel = min(args.loglevels) if args.loglevels else DEFAULT_LOG_LEVEL
    tr = TerminalReporter(runner=runner, is_async=is_async
                         , print_progress=not args.no_progress
                         , succinct=args.succinct
                         , check_threshold=loglevel
                         , log_threshold=args.loglevel_messages or loglevel
                         , theme=theme
                         , collect_results_by=args.gather_by
                         , skip_status_report=None if args.show_sections \
                                                   else (SECTIONSUMMARY, )
                         )
    reporters = [tr.receive]

    if args.json:
        sr = SerializeReporter(runner=runner,
                               collect_results_by=args.gather_by)
        reporters.append(sr.receive)

    if args.ghmarkdown:
        mdr = GHMarkdownReporter(loglevels=args.loglevels,
                                 runner=runner,
                                 collect_results_by=args.gather_by)
        reporters.append(mdr.receive)

    if args.html:
        hr = HTMLReporter(loglevels=args.loglevels,
                          runner=runner,
                          collect_results_by=args.gather_by)
        reporters.append(hr.receive)

    if args.multiprocessing == 0:
        status_generator = runner.run()
    else:
        status_generator = multiprocessing_runner(args.multiprocessing, runner,
                                                  runner_kwds)

    distribute_generator(status_generator, reporters)

    if args.json:
        import json
        json.dump(sr.getdoc(), args.json, sort_keys=True, indent=4)
        print(f'A report in JSON format has been'
              f' saved to "{args.json.name}"')

    if args.ghmarkdown:
        args.ghmarkdown.write(mdr.get_markdown())
        print(f'A report in GitHub Markdown format which can be useful\n'
              f' for posting issues on a GitHub issue tracker has been\n'
              f' saved to "{args.ghmarkdown.name}"')

    if args.html:
        args.html.write(hr.get_html())
        print(f'A report in HTML format has been saved to "{args.html.name}"')

    # Fail and error let the command fail
    return 1 if tr.worst_check_status in (ERROR, FAIL) else 0
def main(specification=None, values=None):
  # specification can be injected by e.g. check-googlefonts injects it's own spec
  add_spec_arg = False
  if specification is None:
    specification = get_spec()
    add_spec_arg = True

  argument_parser, values_keys = ArgumentParser(specification, spec_arg=add_spec_arg)
  args = argument_parser.parse_args()

  if args.list_checks:
    print('Available checks')
    for section_name, section in specification._sections.items():
      checks = section.list_checks()
      message = "# {}:\n  {}".format(section_name,"\n  ".join(checks))
      print(message)
    sys.exit()

  values_ = {}
  if values is not None:
    values_.update(values)

  # values_keys are returned by specification.setup_argparse
  # these are keys for custom arguments required by the spec.
  if values_keys:
    for key in values_keys:
      if hasattr(args, key):
        values_[key] = getattr(args, key)

  try:
    runner = CheckRunner(specification
                        , values=values_
                        , custom_order=args.order
                        , explicit_checks=args.checkid
                        , exclude_checks=args.exclude_checkid
                        )
  except ValueValidationError as e:
    print(e)
    argument_parser.print_usage()
    sys.exit(1)

  # The default Windows Terminal just displays the escape codes. The argument
  # parser above therefore has these options disabled.
  if sys.platform == "win32":
    args.no_progress = True
    args.no_colors = True

  # the most verbose loglevel wins
  loglevel = min(args.loglevels) if args.loglevels else DEFAULT_LOG_LEVEL
  tr = TerminalReporter(runner=runner, is_async=False
                       , print_progress=not args.no_progress
                       , check_threshold=loglevel
                       , log_threshold=args.loglevel_messages or loglevel
                       , usecolor=not args.no_colors
                       , collect_results_by=args.gather_by
                       , skip_status_report=None if args.show_sections\
                                                      else (STARTSECTION, ENDSECTION)

                       )
  reporters = [tr.receive]

  if args.json:
    sr = SerializeReporter(runner=runner, collect_results_by=args.gather_by)
    reporters.append(sr.receive)

  if args.ghmarkdown:
    mdr = GHMarkdownReporter(loglevels=args.loglevels,
                             runner=runner,
                             collect_results_by=args.gather_by)
    reporters.append(mdr.receive)

  distribute_generator(runner.run(), reporters)

  if args.json:
    import json
    json.dump(sr.getdoc(), args.json, sort_keys=True, indent=4)
    print("A report in JSON format has been"
          " saved to '{}'".format(args.json.name))

  if args.ghmarkdown:
    args.ghmarkdown.write(mdr.get_markdown())
    print("A report in GitHub Markdown format which can be useful\n"
          " for posting issues on a GitHub issue tracker has been\n"
          " saved to '{}'".format(args.ghmarkdown.name))

  # Fail and error let the command fail
  return 1 if tr.worst_check_status in (ERROR, FAIL) else 0