#----------------------------------------------------------------------------- # Copyright (c) 2012 - 2021, Anaconda, Inc., and Bokeh Contributors. # All rights reserved. # # The full license is in the file LICENSE.txt, distributed with this software. #----------------------------------------------------------------------------- ''' Provide support modules for testing Bokeh itself. ''' #----------------------------------------------------------------------------- # Boilerplate #----------------------------------------------------------------------------- from __future__ import annotations import logging # isort:skip log = logging.getLogger(__name__) #----------------------------------------------------------------------------- # Imports #----------------------------------------------------------------------------- # Standard library imports import os from base64 import b64decode from os.path import ( basename, dirname, exists, isdir, join, normpath, pardir, relpath, splitext, ) from typing import List, Union # External imports import yaml from typing_extensions import Literal #----------------------------------------------------------------------------- # Globals and constants #----------------------------------------------------------------------------- __all__ = ( 'add_examples', 'collect_examples', 'Example', 'Flags', ) JOB_ID = os.environ.get("GITHUB_ACTION", "local") PathLike = Union[str, bytes, "os.PathLike[str]", "os.PathLike[bytes]"] #----------------------------------------------------------------------------- # General API #----------------------------------------------------------------------------- class Flags: file = 1 << 1 server = 1 << 2 notebook = 1 << 3 slow = 1 << 4 # example needs a lot of time to run (> 30 s) (e.g. choropleth.py) skip = 1 << 5 # don't run example at all (e.g. notebooks are completely broken) xfail = 1 << 6 # test is expected to fail, which doesn't fail the test suite no_js = 1 << 7 # skip bokehjs and thus image diff (e.g. google maps key issue) class Example: def __init__(self, path: str, flags: int, examples_dir: str, extensions: List[str] = []) -> None: self.path = normpath(path) self.flags = flags self.examples_dir = examples_dir self.extensions = extensions self._diff_ref = None self.pixels = 0 self._has_ref = None def __str__(self) -> str: flags = [ "file" if self.is_file else "", "server" if self.is_server else "", "notebook" if self.is_notebook else "", "slow" if self.is_slow else "", "skip" if self.is_skip else "", "xfail" if self.is_xfail else "", "no_js" if self.no_js else "", ] return "Example(%r, %s)" % (self.relpath, "|".join(f for f in flags if f)) __repr__ = __str__ @property def name(self) -> str: return basename(self.path_no_ext) @property def base_dir(self) -> str: return dirname(self.path) @property def relpath(self) -> str: return relpath(self.path, self.examples_dir) @property def path_no_ext(self) -> str: return splitext(self.path)[0] @property def img_path(self) -> str: return self.path_no_ext + ".png" @property def is_file(self) -> bool: return bool(self.flags & Flags.file) @property def is_server(self) -> bool: return bool(self.flags & Flags.server) @property def is_notebook(self) -> bool: return bool(self.flags & Flags.notebook) @property def is_slow(self) -> bool: return bool(self.flags & Flags.slow) @property def is_skip(self) -> bool: return bool(self.flags & Flags.skip) @property def is_xfail(self) -> bool: return bool(self.flags & Flags.xfail) @property def no_js(self) -> bool: return bool(self.flags & Flags.no_js) def store_img(self, img_data: str) -> None: _store_binary(self.img_path, b64decode(img_data)) All = Literal["all"] def add_examples(list_of_examples: List[Example], path: str, examples_dir: str, example_type: int | None = None, slow: List[str] | All | None = None, skip: List[str] | All | None = None, xfail: List[str] | All | None = None, no_js: List[str] | All | None = None) -> None: if path.endswith("*"): star_path = join(examples_dir, path[:-1]) for name in sorted(os.listdir(star_path)): if isdir(join(star_path, name)): add_examples(list_of_examples, join(path[:-1], name), examples_dir, example_type, slow, skip, xfail, no_js) return example_path = normpath(join(examples_dir, path)) for name in sorted(os.listdir(example_path)): flags = 0 extensions: List[str] = [] orig_name = name if name.startswith(('_', '.')): continue elif name.endswith(".py"): flags |= example_type if example_type else Flags.file elif name.endswith(".ipynb"): flags |= Flags.notebook elif isdir(join(example_path, name)): if exists(join(example_path, name, name + ".py")): name = join(name, name + ".py") flags |= example_type if example_type else Flags.file elif exists(join(example_path, name, "main.py")): # name is unchanged and passed as the example name flags |= example_type if example_type else Flags.server else: continue ext_file = "bokeh.ext.json" if exists(join(example_path, orig_name, ext_file)): extensions.append(join(example_path, orig_name)) else: for dir_name in os.listdir(join(example_path, orig_name)): dir_path = join(example_path, orig_name, dir_name) if exists(join(dir_path, ext_file)): extensions.append(dir_path) else: continue if slow is not None and orig_name in slow: flags |= Flags.slow if skip is not None and (skip == 'all' or orig_name in skip): flags |= Flags.skip if xfail is not None and (xfail == 'all' or orig_name in xfail): flags |= Flags.xfail if no_js is not None and (no_js == 'all' or orig_name in no_js): flags |= Flags.no_js list_of_examples.append(Example(join(example_path, name), flags, examples_dir, extensions)) def collect_examples(config_path: str) -> List[Example]: examples_dir = join(dirname(config_path), pardir) list_of_examples: List[Example] = [] with open(config_path, "r") as f: examples = yaml.safe_load(f.read()) for example in examples: path = example["path"] if example.get("type") is not None: example_type = getattr(Flags, example["type"]) else: example_type = None slow_status = example.get("slow") skip_status = example.get("skip") xfail_status = example.get("xfail") no_js_status = example.get("no_js") add_examples(list_of_examples, path, examples_dir, example_type=example_type, slow=slow_status, skip=skip_status, xfail=xfail_status, no_js=no_js_status) return list_of_examples #----------------------------------------------------------------------------- # Dev API #----------------------------------------------------------------------------- #----------------------------------------------------------------------------- # Private API #----------------------------------------------------------------------------- def _store_binary(path: PathLike, data: bytes) -> None: directory = dirname(path) if not exists(directory): os.makedirs(directory) with open(path, "wb") as f: f.write(data) #----------------------------------------------------------------------------- # Code #-----------------------------------------------------------------------------