diff --git a/.coveragerc b/.coveragerc index f593bd0..e809714 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,3 +1,10 @@ [run] omit = tests/* + *mock* + *funcsigs* + *pbr* + *six* + /usr/lib* + +[report] diff --git a/bumblebee/config.py b/bumblebee/config.py index e3b24aa..3086cff 100644 --- a/bumblebee/config.py +++ b/bumblebee/config.py @@ -25,8 +25,6 @@ class print_usage(argparse.Action): self.print_modules() elif value == "themes": self.print_themes() - else: - parser.print_help() sys.exit(0) def print_themes(self): diff --git a/bumblebee/engine.py b/bumblebee/engine.py index 9a9c35f..67234fe 100644 --- a/bumblebee/engine.py +++ b/bumblebee/engine.py @@ -52,6 +52,9 @@ class Module(object): """By default, update() is a NOP""" pass + def update_all(self): + self.update(self._widgets) + def parameter(self, name, default=None): """Return the config parameter 'name' for this module""" name = "{}.{}".format(self.name, name) diff --git a/bumblebee/modules/dnf.py b/bumblebee/modules/dnf.py index 8dc000f..f36dfa9 100644 --- a/bumblebee/modules/dnf.py +++ b/bumblebee/modules/dnf.py @@ -53,9 +53,7 @@ class Module(bumblebee.engine.Module): def __init__(self, engine, config): widget = bumblebee.output.Widget(full_text=self.updates) super(Module, self).__init__(engine, config, widget) - self._next_check = 0 - widget def updates(self, widget): result = [] diff --git a/bumblebee/modules/load.py b/bumblebee/modules/load.py index c44ae5a..4d94ee1 100644 --- a/bumblebee/modules/load.py +++ b/bumblebee/modules/load.py @@ -22,7 +22,7 @@ class Module(bumblebee.engine.Module): self._load = [0, 0, 0] try: self._cpus = multiprocessing.cpu_count() - except multiprocessing.NotImplementedError as e: + except NotImplementedError as e: self._cpus = 1 engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, cmd="gnome-system-monitor") diff --git a/bumblebee/modules/test.py b/bumblebee/modules/test.py index 5e92e0e..e4099c6 100644 --- a/bumblebee/modules/test.py +++ b/bumblebee/modules/test.py @@ -5,6 +5,8 @@ import bumblebee.engine +ALIASES = [ "test-alias" ] + class Module(bumblebee.engine.Module): def __init__(self, engine, config): super(Module, self).__init__(engine, config, diff --git a/bumblebee/theme.py b/bumblebee/theme.py index 779155b..08584c7 100644 --- a/bumblebee/theme.py +++ b/bumblebee/theme.py @@ -90,11 +90,6 @@ class Theme(object): """Return the SBW""" return self._get(widget, "separator-block-width", None) - def loads(self, data): - """Initialize the theme from a JSON string""" - theme = json.loads(data) - self._init(theme) - def _load_icons(self, name): """Load icons for a theme""" path = "{}/icons/".format(theme_path()) diff --git a/bumblebee/util.py b/bumblebee/util.py index 730ed87..07f8091 100644 --- a/bumblebee/util.py +++ b/bumblebee/util.py @@ -15,6 +15,8 @@ def execute(cmd, wait=True): out, _ = proc.communicate() if proc.returncode != 0: raise RuntimeError("{} exited with {}".format(cmd, proc.returncode)) + if type(out) == str: + return out return out.decode("utf-8") return None diff --git a/runtests.sh b/runtests.sh index a267458..60f058e 100755 --- a/runtests.sh +++ b/runtests.sh @@ -1,11 +1,11 @@ #!/bin/sh echo "testing with $(python2 -V 2>&1)" -python2 $(which nosetests) --rednose -v tests/ +python2 $(which nosetests) --rednose -v --with-coverage --cover-erase tests/ if [ $? == 0 ]; then echo echo "testing with $(python3 -V 2>&1)" - python3 $(which nosetests-3) --rednose -v tests/ + python3 $(which nosetests-3) --rednose -v --with-coverage --cover-erase tests/ fi diff --git a/tests/mocks.py b/tests/mocks.py new file mode 100644 index 0000000..f5be753 --- /dev/null +++ b/tests/mocks.py @@ -0,0 +1,141 @@ +# pylint: disable=C0103,C0111 + +import mock +import json +import shlex +import random +import subprocess + +from bumblebee.input import I3BarInput +from bumblebee.output import Widget +from bumblebee.config import Config + +def rand(cnt): + return "".join(random.choice("abcdefghijklmnopqrstuvwxyz0123456789") for i in range(cnt)) + +def setup_test(test, Module): + test._stdin, test._select, test.stdin, test.select = epoll_mock("bumblebee.input") + + test.popen = MockPopen() + + test.config = Config() + test.input = I3BarInput() + test.engine = mock.Mock() + test.engine.input = test.input + test.input.need_event = True + test.module = Module(engine=test.engine, config={ "config": test.config }) + for widget in test.module.widgets(): + widget.link_module(test.module) + test.anyWidget = widget + +def teardown_test(test): + test._stdin.stop() + test._select.stop() + test.popen.cleanup() + +def epoll_mock(module=""): + if len(module) > 0: module = "{}.".format(module) + + stdin = mock.patch("{}sys.stdin".format(module)) + select = mock.patch("{}select".format(module)) + epoll = mock.Mock() + + stdin_mock = stdin.start() + select_mock = select.start() + + stdin_mock.fileno.return_value = 1 + select_mock.epoll.return_value = epoll + epoll.poll.return_value = [(stdin_mock.fileno.return_value, 100)] + + return stdin, select, stdin_mock, select_mock + +def mouseEvent(stdin, button, inp, module=None, instance=None): + stdin.readline.return_value = json.dumps({ + "name": module.id if module else rand(10), + "button": button, + "instance": instance + }) + inp.start() + inp.stop() + stdin.readline.assert_any_call() + +class MockPopen(object): + def __init__(self, module=""): + if len(module) > 0: module = "{}.".format(module) + self._patch = mock.patch("{}subprocess.Popen".format(module)) + self._popen = self._patch.start() + self.mock = mock.Mock() + # for a nicer, more uniform interface + self.mock.popen = self._popen + # for easier command execution checks + self.mock.popen.assert_call = self.assert_call + self._popen.return_value = self.mock + + self.mock.communicate.return_value = [ "", None ] + self.mock.returncode = 0 + + def assert_call(self, cmd): + self.mock.popen.assert_any_call(shlex.split(cmd), stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + + def cleanup(self): + self._patch.stop() + +class MockInput(object): + def __init__(self): + self._callbacks = {} + def start(self): + pass + + def stop(self): + pass + + def get_callback(self, uid): + return self._callbacks.get(uid, None) + + def register_callback(self, obj, button, cmd): + if not obj: + return + self._callbacks[obj.id] = { + "button": button, + "command": cmd, + } + +class MockOutput(object): + def start(self): + pass + + def stop(self): + pass + + def draw(self, widget, engine, module): + engine.stop() + + def begin(self): + pass + + def flush(self): + pass + + def end(self): + pass + +class MockEngine(object): + def __init__(self): + self.input = MockInput() + +class MockWidget(Widget): + def __init__(self, text): + super(MockWidget, self).__init__(text) + self.module = None + self.attr_state = ["state-default"] + self.id = rand(10) + + self.full_text(text) + +# def state(self): +# return self.attr_state + + def update(self, widgets): + pass + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/tests/modules/test_battery.py b/tests/modules/test_battery.py index 499a0fa..0c86cc9 100644 --- a/tests/modules/test_battery.py +++ b/tests/modules/test_battery.py @@ -1,68 +1,108 @@ # pylint: disable=C0103,C0111 import sys -import json -import unittest import mock +import unittest -from contextlib import contextmanager +try: + from StringIO import StringIO +except ImportError: + from io import StringIO + +import tests.mocks as mocks -import bumblebee.input -from bumblebee.input import I3BarInput from bumblebee.modules.battery import Module -from tests.util import MockEngine, MockConfig, assertPopen - -class MockOpen(object): - def __init__(self): - self._value = "" - - def returns(self, value): - self._value = value - - def __enter__(self): - return self - - def __exit__(self, a, b, c): - pass - - def read(self): - return self._value +from bumblebee.config import Config class TestBatteryModule(unittest.TestCase): def setUp(self): - self.engine = MockEngine() - self.config = MockConfig() - self.module = Module(engine=self.engine, config={ "config": self.config }) + self._stdout = mock.patch("sys.stdout", new_callable=StringIO) + self._exists = mock.patch("bumblebee.modules.battery.os.path.exists") + self._open = mock.patch("bumblebee.modules.battery.open", create=True) + + self.stdout = self._stdout.start() + self.exists = self._exists.start() + self.open = self._open.start() + self.file = mock.Mock() + self.file.__enter__ = lambda x: self.file + self.file.__exit__ = lambda x, a, b, c: "" + self.open.return_value = self.file + + self.exists.return_value = True + self.engine = mock.Mock() + self.config = Config() + self.module = Module(engine=self.engine, config={"config":self.config}) + + self.config.set("battery.critical", "20") + self.config.set("battery.warning", "25") + self.criticalValue = "19" + self.warningValue = "21" + self.normalValue = "26" + self.chargedValue = "96" + for widget in self.module.widgets(): widget.link_module(self.module) + self.anyWidget = widget - @mock.patch("sys.stdout") - def test_format(self, mock_output): + def tearDown(self): + self._stdout.stop() + self._exists.stop() + self._open.stop() + + def test_format(self): for widget in self.module.widgets(): self.assertEquals(len(widget.full_text()), len("100%")) - @mock.patch("os.path.exists") - @mock.patch("{}.open".format("__builtin__" if sys.version_info[0] < 3 else "builtins")) - @mock.patch("subprocess.Popen") - def test_critical(self, mock_output, mock_open, mock_exists): - mock_open.return_value = MockOpen() - mock_open.return_value.returns("19") - mock_exists.return_value = True - self.config.set("battery.critical", "20") - self.config.set("battery.warning", "25") - self.module.update(self.module.widgets()) - self.assertTrue("critical" in self.module.widgets()[0].state()) + def test_critical(self): + self.file.read.return_value = self.criticalValue + self.module.update_all() + self.assertTrue("critical" in self.module.state(self.anyWidget)) - @mock.patch("os.path.exists") - @mock.patch("{}.open".format("__builtin__" if sys.version_info[0] < 3 else "builtins")) - @mock.patch("subprocess.Popen") - def test_warning(self, mock_output, mock_open, mock_exists): - mock_open.return_value = MockOpen() - mock_exists.return_value = True - mock_open.return_value.returns("22") - self.config.set("battery.critical", "20") - self.config.set("battery.warning", "25") - self.module.update(self.module.widgets()) - self.assertTrue("warning" in self.module.widgets()[0].state()) + def test_warning(self): + self.file.read.return_value = self.warningValue + self.module.update_all() + self.assertTrue("warning" in self.module.state(self.anyWidget)) + + def test_normal(self): + self.file.read.return_value = self.normalValue + self.module.update_all() + self.assertTrue(not "warning" in self.module.state(self.anyWidget)) + self.assertTrue(not "critical" in self.module.state(self.anyWidget)) + + def test_overload(self): + self.file.read.return_value = "120" + self.module.update_all() + self.assertTrue(not "warning" in self.module.state(self.anyWidget)) + self.assertTrue(not "critical" in self.module.state(self.anyWidget)) + self.assertEquals(self.module.capacity(self.anyWidget), "100%") + + def test_ac(self): + self.exists.return_value = False + self.module.update_all() + self.assertEquals(self.module.capacity(self.anyWidget), "ac") + self.assertTrue("AC" in self.module.state(self.anyWidget)) + + def test_error(self): + self.file.read.side_effect = IOError("failed to read") + self.module.update_all() + self.assertEquals(self.module.capacity(self.anyWidget), "n/a") + self.assertTrue("critical" in self.module.state(self.anyWidget)) + self.assertTrue("unknown" in self.module.state(self.anyWidget)) + + def test_charging(self): + self.file.read.return_value = self.chargedValue + self.module.update_all() + self.assertTrue("charged" in self.module.state(self.anyWidget)) + self.file.read.return_value = self.normalValue + self.module.update_all() + self.assertTrue("charging" in self.module.state(self.anyWidget)) + + def test_discharging(self): + for limit in [ 10, 25, 50, 80, 100 ]: + value = limit - 1 + self.file.read.return_value = str(value) + self.module.update_all() + self.file.read.return_value = "Discharging" + self.assertTrue("discharging-{}".format(limit) in self.module.state(self.anyWidget)) # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/tests/modules/test_brightness.py b/tests/modules/test_brightness.py index b14af4b..b636d44 100644 --- a/tests/modules/test_brightness.py +++ b/tests/modules/test_brightness.py @@ -1,56 +1,47 @@ # pylint: disable=C0103,C0111 -import json -import unittest import mock +import unittest -import bumblebee.input -from bumblebee.input import I3BarInput +try: + from StringIO import StringIO +except ImportError: + from io import StringIO + +import tests.mocks as mocks + +from bumblebee.input import WHEEL_UP, WHEEL_DOWN from bumblebee.modules.brightness import Module -from tests.util import MockEngine, MockConfig, assertPopen, assertMouseEvent class TestBrightnessModule(unittest.TestCase): def setUp(self): - self.engine = MockEngine() - self.engine.input = I3BarInput() - self.engine.input.need_event = True - self.config = MockConfig() - self.module = Module(engine=self.engine, config={ "config": self.config }) - for widget in self.module.widgets(): - widget.link_module(self.module) + mocks.setup_test(self, Module) - @mock.patch("sys.stdout") - def test_format(self, mock_output): + def tearDown(self): + mocks.teardown_test(self) + + def test_format(self): for widget in self.module.widgets(): self.assertEquals(len(widget.full_text()), len("100%")) - @mock.patch("select.epoll") - @mock.patch("subprocess.Popen") - @mock.patch("sys.stdin") - def test_wheel_up(self, mock_input, mock_output, mock_select): - assertMouseEvent(mock_input, mock_output, mock_select, self.engine, - self.module, bumblebee.input.WHEEL_UP, - "xbacklight +2%" - ) + def test_wheel_up(self): + mocks.mouseEvent(stdin=self.stdin, button=WHEEL_UP, inp=self.input, module=self.module) + self.popen.assert_call("xbacklight +2%") - @mock.patch("select.epoll") - @mock.patch("subprocess.Popen") - @mock.patch("sys.stdin") - def test_wheel_down(self, mock_input, mock_output, mock_select): - assertMouseEvent(mock_input, mock_output, mock_select, self.engine, - self.module, bumblebee.input.WHEEL_DOWN, - "xbacklight -2%" - ) + def test_wheel_down(self): + mocks.mouseEvent(stdin=self.stdin, button=WHEEL_DOWN, inp=self.input, module=self.module) + self.popen.assert_call("xbacklight -2%") - @mock.patch("select.epoll") - @mock.patch("subprocess.Popen") - @mock.patch("sys.stdin") - def test_custom_step(self, mock_input, mock_output, mock_select): + def test_custom_step(self): self.config.set("brightness.step", "10") - module = Module(engine=self.engine, config={ "config": self.config }) - assertMouseEvent(mock_input, mock_output, mock_select, self.engine, - module, bumblebee.input.WHEEL_DOWN, - "xbacklight -10%" - ) + module = Module(engine=self.engine, config={"config": self.config}) + mocks.mouseEvent(stdin=self.stdin, button=WHEEL_DOWN, inp=self.input, module=module) + self.popen.assert_call("xbacklight -10%") + + def test_update(self): + self.popen.mock.communicate.return_value = ("20.0", None) + self.module.update_all() + self.assertEquals(self.module.brightness(self.anyWidget), "020%") + # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/tests/modules/test_caffeine.py b/tests/modules/test_caffeine.py index 995eece..e95e2ac 100644 --- a/tests/modules/test_caffeine.py +++ b/tests/modules/test_caffeine.py @@ -4,18 +4,50 @@ import json import unittest import mock -import bumblebee.input -from bumblebee.input import I3BarInput +try: + from StringIO import StringIO +except ImportError: + from io import StringIO + +import tests.mocks as mocks + +from bumblebee.config import Config +from bumblebee.input import LEFT_MOUSE from bumblebee.modules.caffeine import Module -from tests.util import MockEngine, MockConfig, assertPopen class TestCaffeineModule(unittest.TestCase): def setUp(self): - self.engine = MockEngine() - self.engine.input = I3BarInput() - self.engine.input.need_event = True - self.engine.input.need_valid_event = True - self.config = MockConfig() - self.module = Module(engine=self.engine, config={ "config": self.config }) - for widget in self.module.widgets(): - widget.link_module(self.module) + mocks.setup_test(self, Module) + + self.xset_active = " timeout: 0 cycle: 123" + self.xset_inactive = " timeout: 600 cycle: 123" + + def tearDown(self): + mocks.teardown_test(self) + + def test_text(self): + self.assertEquals(self.module.caffeine(self.anyWidget), "") + + def test_active(self): + self.popen.mock.communicate.return_value = (self.xset_active, None) + self.assertTrue(not "deactivated" in self.module.state(self.anyWidget)) + self.assertTrue("activated" in self.module.state(self.anyWidget)) + + def test_inactive(self): + self.popen.mock.communicate.return_value = (self.xset_inactive, None) + self.assertTrue("deactivated" in self.module.state(self.anyWidget)) + self.popen.mock.communicate.return_value = ("no text", None) + self.assertTrue("deactivated" in self.module.state(self.anyWidget)) + + def test_toggle(self): + self.popen.mock.communicate.return_value = (self.xset_active, None) + mocks.mouseEvent(stdin=self.stdin, button=LEFT_MOUSE, inp=self.input, module=self.module) + self.popen.assert_call("xset s default") + self.popen.assert_call("notify-send \"Out of coffee\"") + + self.popen.mock.communicate.return_value = (self.xset_inactive, None) + mocks.mouseEvent(stdin=self.stdin, button=LEFT_MOUSE, inp=self.input, module=self.module) + self.popen.assert_call("xset s off") + self.popen.assert_call("notify-send \"Consuming caffeine\"") + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/tests/modules/test_cmus.py b/tests/modules/test_cmus.py index 58be4bc..e7118d1 100644 --- a/tests/modules/test_cmus.py +++ b/tests/modules/test_cmus.py @@ -1,38 +1,93 @@ # pylint: disable=C0103,C0111 -import json -import unittest import mock +import unittest -import bumblebee.input -from bumblebee.input import I3BarInput +import tests.mocks as mocks + +from bumblebee.input import LEFT_MOUSE from bumblebee.modules.cmus import Module -from tests.util import MockEngine, MockConfig, assertPopen, MockEpoll class TestCmusModule(unittest.TestCase): def setUp(self): - self.engine = MockEngine() - self.engine.input = I3BarInput() - self.engine.input.need_event = True - self.module = Module(engine=self.engine, config={"config": MockConfig()}) + mocks.setup_test(self, Module) - @mock.patch("subprocess.Popen") - def test_read_song(self, mock_output): - rv = mock.Mock() - rv.configure_mock(**{ - "communicate.return_value": ("out", None) - }) - mock_output.return_value = rv - self.module.update(self.module.widgets()) - assertPopen(mock_output, "cmus-remote -Q") + self.songTemplate = """ +status {status} +file /path/to/file +duration {duration} +position {position} +tag title {title} +tag artist {artist} +tag album {album} +tag tracknumber 1 +tag date 1984 +tag comment comment + """ - def test_widgets(self): - self.assertTrue(len(self.module.widgets()), 5) + def tearDown(self): + mocks.teardown_test(self) - @mock.patch("select.epoll") - @mock.patch("subprocess.Popen") - @mock.patch("sys.stdin") - def test_interaction(self, mock_input, mock_output, mock_select): + def test_read_song(self): + self.popen.mock.communicate.return_value = ("song", None) + self.module.update_all() + self.popen.assert_call("cmus-remote -Q") + + def test_handle_runtimeerror(self): + self.popen.mock.communicate.side_effect = RuntimeError("error loading song") + self.module.update_all() + self.assertEquals(self.module.description(self.anyWidget), " - /") + + def test_format(self): + self.popen.mock.communicate.return_value = (self.songTemplate.format( + artist="an artist", title="a title", duration="100", position="20", + album="an album", status="irrelevant" + ), None) + self.module.update_all() + self.assertEquals(self.module.description(self.anyWidget), + "an artist - a title 00:20/01:40" + ) + + def test_repeat(self): + self.popen.mock.communicate.return_value = ("set repeat false", None) + self.module.update_all() + self.assertTrue("repeat-off" in self.module.state(self.module.widget("cmus.repeat"))) + self.popen.mock.communicate.return_value = ("set repeat true", None) + self.module.update_all() + self.assertTrue("repeat-on" in self.module.state(self.module.widget("cmus.repeat"))) + + def test_shuffle(self): + self.popen.mock.communicate.return_value = ("set shuffle false", None) + self.module.update_all() + self.assertTrue("shuffle-off" in self.module.state(self.module.widget("cmus.shuffle"))) + self.popen.mock.communicate.return_value = ("set shuffle true", None) + self.module.update_all() + self.assertTrue("shuffle-on" in self.module.state(self.module.widget("cmus.shuffle"))) + + def test_prevnext(self): + self.assertTrue("prev" in self.module.state(self.module.widget("cmus.prev"))) + self.assertTrue("next" in self.module.state(self.module.widget("cmus.next"))) + + def test_main(self): + self.popen.mock.communicate.return_value = ("status paused", None) + self.module.update_all() + self.assertTrue("paused" in self.module.state(self.module.widget("cmus.main"))) + + self.popen.mock.communicate.return_value = ("status playing", None) + self.module.update_all() + self.assertTrue("playing" in self.module.state(self.module.widget("cmus.main"))) + + self.popen.mock.communicate.return_value = ("status stopped", None) + self.module.update_all() + self.assertTrue("stopped" in self.module.state(self.module.widget("cmus.main"))) + + def test_widget(self): + self.assertEquals(len(self.module.widgets()), 5) + + for idx, val in enumerate(["prev", "main", "next", "shuffle", "repeat"]): + self.assertEquals(self.module.widgets()[idx].name, "cmus.{}".format(val)) + + def test_interaction(self): events = [ {"widget": "cmus.shuffle", "action": "cmus-remote -S"}, {"widget": "cmus.repeat", "action": "cmus-remote -R"}, @@ -40,19 +95,8 @@ class TestCmusModule(unittest.TestCase): {"widget": "cmus.prev", "action": "cmus-remote -r"}, {"widget": "cmus.main", "action": "cmus-remote -u"}, ] - - mock_input.fileno.return_value = 1 - mock_select.return_value = MockEpoll() - for event in events: - mock_input.readline.return_value = json.dumps({ - "name": self.module.id, - "button": bumblebee.input.LEFT_MOUSE, - "instance": self.module.widget(event["widget"]).id - }) - self.engine.input.start() - self.engine.input.stop() - mock_input.readline.assert_any_call() - assertPopen(mock_output, event["action"]) + mocks.mouseEvent(stdin=self.stdin, inp=self.input, module=self.module, instance=self.module.widget(event["widget"]).id, button=LEFT_MOUSE) + self.popen.assert_call(event["action"]) # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/tests/modules/test_cpu.py b/tests/modules/test_cpu.py index fea056e..cd5ce89 100644 --- a/tests/modules/test_cpu.py +++ b/tests/modules/test_cpu.py @@ -1,48 +1,45 @@ # pylint: disable=C0103,C0111 -import json -import unittest import mock +import unittest -import bumblebee.input -from bumblebee.input import I3BarInput +import tests.mocks as mocks + +from bumblebee.input import LEFT_MOUSE from bumblebee.modules.cpu import Module -from tests.util import MockEngine, MockConfig, assertPopen, assertMouseEvent, assertStateContains class TestCPUModule(unittest.TestCase): def setUp(self): - self.engine = MockEngine() - self.engine.input = I3BarInput() - self.engine.input.need_event = True - self.config = MockConfig() - self.module = Module(engine=self.engine, config={ "config": self.config }) + mocks.setup_test(self, Module) + self._psutil = mock.patch("bumblebee.modules.cpu.psutil") + self.psutil = self._psutil.start() - @mock.patch("sys.stdout") - def test_format(self, mock_output): + def tearDown(self): + self._psutil.stop() + mocks.teardown_test(self) + + def test_format(self): + self.psutil.cpu_percent.return_value = 21.0 + self.module.update_all() for widget in self.module.widgets(): self.assertEquals(len(widget.full_text()), len("100.00%")) - @mock.patch("select.epoll") - @mock.patch("subprocess.Popen") - @mock.patch("sys.stdin") - def test_leftclick(self, mock_input, mock_output, mock_select): - assertMouseEvent(mock_input, mock_output, mock_select, self.engine, - self.module, bumblebee.input.LEFT_MOUSE, - "gnome-system-monitor" - ) + def test_leftclick(self): + mocks.mouseEvent(stdin=self.stdin, button=LEFT_MOUSE, inp=self.input, module=self.module) + self.popen.assert_call("gnome-system-monitor") - @mock.patch("psutil.cpu_percent") - def test_warning(self, mock_psutil): + def test_warning(self): self.config.set("cpu.critical", "20") self.config.set("cpu.warning", "18") - mock_psutil.return_value = 19.0 - assertStateContains(self, self.module, "warning") + self.psutil.cpu_percent.return_value = 19.0 + self.module.update_all() + self.assertTrue("warning" in self.module.state(self.anyWidget)) - @mock.patch("psutil.cpu_percent") - def test_critical(self, mock_psutil): + def test_critical(self): self.config.set("cpu.critical", "20") self.config.set("cpu.warning", "19") - mock_psutil.return_value = 21.0 - assertStateContains(self, self.module, "critical") + self.psutil.cpu_percent.return_value = 21.0 + self.module.update_all() + self.assertTrue("critical" in self.module.state(self.anyWidget)) # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/tests/modules/test_disk.py b/tests/modules/test_disk.py index 423612e..d7e3370 100644 --- a/tests/modules/test_disk.py +++ b/tests/modules/test_disk.py @@ -1,13 +1,12 @@ # pylint: disable=C0103,C0111 -import json -import unittest import mock +import unittest -import bumblebee.input -from bumblebee.input import I3BarInput +import tests.mocks as mocks + +from bumblebee.input import LEFT_MOUSE from bumblebee.modules.disk import Module -from tests.util import MockEngine, MockConfig, assertPopen, assertStateContains, MockEpoll class MockVFS(object): def __init__(self, perc): @@ -17,40 +16,32 @@ class MockVFS(object): class TestDiskModule(unittest.TestCase): def setUp(self): - self.engine = MockEngine() - self.engine.input = I3BarInput() - self.engine.input.need_event = True - self.config = MockConfig() + mocks.setup_test(self, Module) + self._os = mock.patch("bumblebee.modules.disk.os") + self.os = self._os.start() self.config.set("disk.path", "somepath") - self.module = Module(engine=self.engine, config={"config": self.config}) - @mock.patch("select.epoll") - @mock.patch("subprocess.Popen") - @mock.patch("sys.stdin") - def test_leftclick(self, mock_input, mock_output, mock_select): - mock_input.readline.return_value = json.dumps({ - "name": self.module.id, - "button": bumblebee.input.LEFT_MOUSE, - "instance": None - }) - mock_select.return_value = MockEpoll() - self.engine.input.start() - self.engine.input.stop() - mock_input.readline.assert_any_call() - assertPopen(mock_output, "nautilus {}".format(self.module.parameter("path"))) + def tearDown(self): + self._os.stop() + mocks.teardown_test(self) - @mock.patch("os.statvfs") - def test_warning(self, mock_stat): + def test_leftclick(self): + module = Module(engine=self.engine, config={"config":self.config}) + mocks.mouseEvent(stdin=self.stdin, button=LEFT_MOUSE, inp=self.input, module=module) + self.popen.assert_call("nautilus {}".format(self.module.parameter("path"))) + + def test_warning(self): self.config.set("disk.critical", "80") self.config.set("disk.warning", "70") - mock_stat.return_value = MockVFS(75.0) - assertStateContains(self, self.module, "warning") + self.os.statvfs.return_value = MockVFS(75.0) + self.module.update_all() + self.assertTrue("warning" in self.module.state(self.anyWidget)) - @mock.patch("os.statvfs") - def test_critical(self, mock_stat): + def test_critical(self): self.config.set("disk.critical", "80") self.config.set("disk.warning", "70") - mock_stat.return_value = MockVFS(85.0) - assertStateContains(self, self.module, "critical") + self.os.statvfs.return_value = MockVFS(85.0) + self.module.update_all() + self.assertTrue("critical" in self.module.state(self.anyWidget)) # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/tests/modules/test_load.py b/tests/modules/test_load.py index e4ed353..e57bc2b 100644 --- a/tests/modules/test_load.py +++ b/tests/modules/test_load.py @@ -1,47 +1,57 @@ # pylint: disable=C0103,C0111 import json -import unittest import mock +import unittest -import bumblebee.input -from bumblebee.input import I3BarInput +import tests.mocks as mocks + +from bumblebee.input import LEFT_MOUSE from bumblebee.modules.load import Module -from tests.util import MockEngine, MockConfig, assertStateContains, assertMouseEvent class TestLoadModule(unittest.TestCase): def setUp(self): - self.engine = MockEngine() - self.engine.input = I3BarInput() - self.engine.input.need_event = True - self.config = MockConfig() - self.module = Module(engine=self.engine, config={ "config": self.config }) + mocks.setup_test(self, Module) - @mock.patch("select.epoll") - @mock.patch("subprocess.Popen") - @mock.patch("sys.stdin") - def test_leftclick(self, mock_input, mock_output, mock_select): - assertMouseEvent(mock_input, mock_output, mock_select, self.engine, - self.module, bumblebee.input.LEFT_MOUSE, - "gnome-system-monitor" - ) + self._mp = mock.patch("bumblebee.modules.load.multiprocessing") + self._os = mock.patch("bumblebee.modules.load.os") - @mock.patch("multiprocessing.cpu_count") - @mock.patch("os.getloadavg") - def test_warning(self, mock_loadavg, mock_cpucount): + self.mp = self._mp.start() + self.os = self._os.start() + + self.mp.cpu_count.return_value = 1 + + def tearDown(self): + self._mp.stop() + self._os.stop() + mocks.teardown_test(self) + + def test_leftclick(self): + mocks.mouseEvent(stdin=self.stdin, button=LEFT_MOUSE, inp=self.input, module=self.module) + self.popen.assert_call("gnome-system-monitor") + + def test_load_format(self): + self.os.getloadavg.return_value = [ 5.9, 1.2, 0.8 ] + self.module.update_all() + self.assertEquals(self.module.load(self.anyWidget), "5.90/1.20/0.80") + + def test_warning(self): self.config.set("load.critical", "1") self.config.set("load.warning", "0.8") - mock_cpucount.return_value = 1 - mock_loadavg.return_value = [ 0.9, 0, 0 ] - assertStateContains(self, self.module, "warning") + self.os.getloadavg.return_value = [ 0.9, 0, 0 ] + self.module.update_all() + self.assertTrue("warning" in self.module.state(self.anyWidget)) - @mock.patch("multiprocessing.cpu_count") - @mock.patch("os.getloadavg") - def test_critical(self, mock_loadavg, mock_cpucount): + def test_critical(self): self.config.set("load.critical", "1") self.config.set("load.warning", "0.8") - mock_cpucount.return_value = 1 - mock_loadavg.return_value = [ 1.1, 0, 0 ] - assertStateContains(self, self.module, "critical") + self.os.getloadavg.return_value = [ 1.1, 0, 0 ] + self.module.update_all() + self.assertTrue("critical" in self.module.state(self.anyWidget)) + + def test_assume_single_core(self): + self.mp.cpu_count.side_effect = NotImplementedError + module = Module(engine=self.engine, config={"config": mock.Mock() }) + self.assertEquals(1, module._cpus) # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/tests/modules/test_memory.py b/tests/modules/test_memory.py index b32205d..94c61f9 100644 --- a/tests/modules/test_memory.py +++ b/tests/modules/test_memory.py @@ -1,13 +1,12 @@ # pylint: disable=C0103,C0111 -import json -import unittest import mock +import unittest -import bumblebee.input -from bumblebee.input import I3BarInput +import tests.mocks as mocks + +from bumblebee.input import LEFT_MOUSE from bumblebee.modules.memory import Module -from tests.util import MockEngine, MockConfig, assertPopen, assertMouseEvent, assertStateContains class VirtualMemory(object): def __init__(self, percent): @@ -15,33 +14,39 @@ class VirtualMemory(object): class TestMemoryModule(unittest.TestCase): def setUp(self): - self.engine = MockEngine() - self.engine.input = I3BarInput() - self.engine.input.need_event = True - self.config = MockConfig() - self.module = Module(engine=self.engine, config={ "config": self.config }) + mocks.setup_test(self, Module) + self._psutil = mock.patch("bumblebee.modules.memory.psutil") + self.psutil = self._psutil.start() - @mock.patch("select.epoll") - @mock.patch("subprocess.Popen") - @mock.patch("sys.stdin") - def test_leftclick(self, mock_input, mock_output, mock_select): - assertMouseEvent(mock_input, mock_output, mock_select, self.engine, - self.module, bumblebee.input.LEFT_MOUSE, - "gnome-system-monitor" - ) + def tearDown(self): + self._psutil.stop() + mocks.teardown_test(self) - @mock.patch("psutil.virtual_memory") - def test_warning(self, mock_vmem): + def test_leftclick(self): + mocks.mouseEvent(stdin=self.stdin, button=LEFT_MOUSE, inp=self.input, module=self.module) + self.popen.assert_call("gnome-system-monitor") + + def test_warning(self): self.config.set("memory.critical", "80") self.config.set("memory.warning", "70") - mock_vmem.return_value = VirtualMemory(75) - assertStateContains(self, self.module, "warning") + self.psutil.virtual_memory.return_value = VirtualMemory(75) + self.module.update_all() + self.assertTrue("warning" in self.module.state(self.anyWidget)) - @mock.patch("psutil.virtual_memory") - def test_critical(self, mock_vmem): + def test_critical(self): self.config.set("memory.critical", "80") self.config.set("memory.warning", "70") - mock_vmem.return_value = VirtualMemory(85) - assertStateContains(self, self.module, "critical") + self.psutil.virtual_memory.return_value = VirtualMemory(81) + self.module.update_all() + self.assertTrue("critical" in self.module.state(self.anyWidget)) + + def test_usage(self): + rv = VirtualMemory(50) + rv.total = 1000 + rv.available = 500 + self.psutil.virtual_memory.return_value = rv + self.module.update_all() + self.assertEquals("500.00B/1000.00B (50.00%)", self.module.memory_usage(self.anyWidget)) + self.assertEquals(None, self.module.state(self.anyWidget)) # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/tests/modules/test_pulseaudio.py b/tests/modules/test_pulseaudio.py index 5aae761..f8754c6 100644 --- a/tests/modules/test_pulseaudio.py +++ b/tests/modules/test_pulseaudio.py @@ -1,56 +1,34 @@ # pylint: disable=C0103,C0111 -import json -import unittest import mock +import unittest -import bumblebee.input -from bumblebee.input import I3BarInput +import tests.mocks as mocks + +from bumblebee.input import LEFT_MOUSE, RIGHT_MOUSE, WHEEL_UP, WHEEL_DOWN from bumblebee.modules.pulseaudio import Module -from tests.util import MockEngine, MockConfig, assertPopen, assertMouseEvent, assertStateContains class TestPulseAudioModule(unittest.TestCase): def setUp(self): - self.engine = MockEngine() - self.engine.input = I3BarInput() - self.engine.input.need_event = True - self.config = MockConfig() - self.module = Module(engine=self.engine, config={ "config": self.config }) + mocks.setup_test(self, Module) - @mock.patch("select.epoll") - @mock.patch("subprocess.Popen") - @mock.patch("sys.stdin") - def test_leftclick(self, mock_input, mock_output, mock_select): - assertMouseEvent(mock_input, mock_output, mock_select, self.engine, - self.module, bumblebee.input.LEFT_MOUSE, - "pactl set-source-mute @DEFAULT_SOURCE@ toggle" - ) + def tearDown(self): + mocks.teardown_test(self) - @mock.patch("select.epoll") - @mock.patch("subprocess.Popen") - @mock.patch("sys.stdin") - def test_rightclick(self, mock_input, mock_output, mock_select): - assertMouseEvent(mock_input, mock_output, mock_select, self.engine, - self.module, bumblebee.input.RIGHT_MOUSE, - "pavucontrol" - ) + def test_leftclick(self): + mocks.mouseEvent(stdin=self.stdin, button=LEFT_MOUSE, inp=self.input, module=self.module) + self.popen.assert_call("pactl set-source-mute @DEFAULT_SOURCE@ toggle") - @mock.patch("select.epoll") - @mock.patch("subprocess.Popen") - @mock.patch("sys.stdin") - def test_wheelup(self, mock_input, mock_output, mock_select): - assertMouseEvent(mock_input, mock_output, mock_select, self.engine, - self.module, bumblebee.input.WHEEL_UP, - "pactl set-source-volume @DEFAULT_SOURCE@ +2%" - ) + def test_rightclick(self): + mocks.mouseEvent(stdin=self.stdin, button=RIGHT_MOUSE, inp=self.input, module=self.module) + self.popen.assert_call("pavucontrol") - @mock.patch("select.epoll") - @mock.patch("subprocess.Popen") - @mock.patch("sys.stdin") - def test_wheeldown(self, mock_input, mock_output, mock_select): - assertMouseEvent(mock_input, mock_output, mock_select, self.engine, - self.module, bumblebee.input.WHEEL_DOWN, - "pactl set-source-volume @DEFAULT_SOURCE@ -2%" - ) + def test_wheelup(self): + mocks.mouseEvent(stdin=self.stdin, button=WHEEL_UP, inp=self.input, module=self.module) + self.popen.assert_call("pactl set-source-volume @DEFAULT_SOURCE@ +2%") + + def test_wheeldown(self): + mocks.mouseEvent(stdin=self.stdin, button=WHEEL_DOWN, inp=self.input, module=self.module) + self.popen.assert_call("pactl set-source-volume @DEFAULT_SOURCE@ -2%") # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/tests/test_config.py b/tests/test_config.py index e7a05f9..395dcb7 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -13,9 +13,20 @@ from bumblebee.engine import all_modules class TestConfig(unittest.TestCase): def setUp(self): + self._stdout = mock.patch("bumblebee.config.sys.stdout", new_callable=StringIO) + self._stderr = mock.patch("bumblebee.config.sys.stderr", new_callable=StringIO) + + self.stdout = self._stdout.start() + self.stderr = self._stderr.start() + self.defaultConfig = Config() self.someSimpleModules = ["foo", "bar", "baz"] self.someAliasModules = ["foo:a", "bar:b", "baz:c"] + self.someTheme = "some-theme" + + def tearDown(self): + self._stdout.stop() + self._stderr.stop() def test_no_modules_by_default(self): self.assertEquals(self.defaultConfig.modules(), []) @@ -33,20 +44,34 @@ class TestConfig(unittest.TestCase): "name": x.split(":")[1], } for x in self.someAliasModules]) - @mock.patch("sys.stdout", new_callable=StringIO) - @mock.patch("sys.exit") - def test_list_themes(self, exit, stdout): - cfg = Config(["-l", "themes"]) - result = stdout.getvalue() + def test_parameters(self): + cfg = Config(["-m", "module", "-p", "module.key=value"]) + self.assertEquals(cfg.get("module.key"), "value") + + def test_theme(self): + cfg = Config(["-t", self.someTheme]) + self.assertEquals(cfg.theme(), self.someTheme) + + def test_notheme(self): + self.assertEquals(self.defaultConfig.theme(), "default") + + def test_list_themes(self): + with self.assertRaises(SystemExit): + cfg = Config(["-l", "themes"]) + result = self.stdout.getvalue() for theme in themes(): self.assertTrue(theme in result) - @mock.patch("sys.stdout", new_callable=StringIO) - @mock.patch("sys.exit") - def test_list_modules(self, exit, stdout): - cfg = Config(["-l", "modules"]) - result = stdout.getvalue() + def test_list_modules(self): + with self.assertRaises(SystemExit): + cfg = Config(["-l", "modules"]) + result = self.stdout.getvalue() for module in all_modules(): self.assertTrue(module["name"] in result) + def test_invalid_list(self): + with self.assertRaises(SystemExit): + cfg = Config(["-l", "invalid"]) + self.assertTrue("invalid choice" in "".join(self.stderr.getvalue())) + # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/tests/test_engine.py b/tests/test_engine.py index 0b71fd4..946f604 100644 --- a/tests/test_engine.py +++ b/tests/test_engine.py @@ -8,13 +8,15 @@ from bumblebee.engine import Engine from bumblebee.config import Config import bumblebee.input -from tests.util import MockOutput, MockInput +from tests.mocks import MockOutput, MockInput class TestEngine(unittest.TestCase): def setUp(self): self.engine = Engine(config=Config(), output=MockOutput(), inp=MockInput()) - self.singleWidgetModule = [{"module": "test", "name": "a"}] self.testModule = "test" + self.testAlias = "test-alias" + self.singleWidgetModule = [{"module": self.testModule, "name": "a"}] + self.singleWidgetAlias = [{"module": self.testAlias, "name": "a" }] self.invalidModule = "no-such-module" self.testModuleSpec = "bumblebee.modules.{}".format(self.testModule) self.testModules = [ @@ -54,6 +56,11 @@ class TestEngine(unittest.TestCase): except Exception as e: self.fail(e) + def test_aliases(self): + modules = self.engine.load_modules(self.singleWidgetAlias) + self.assertEquals(len(modules), 1) + self.assertEquals(modules[0].__module__, self.testModuleSpec) + def test_custom_cmd(self): testmodules = [ { "name": "test", "button": "test.left-click", "action": "echo" }, diff --git a/tests/test_i3barinput.py b/tests/test_i3barinput.py index ca9892f..6852f97 100644 --- a/tests/test_i3barinput.py +++ b/tests/test_i3barinput.py @@ -1,129 +1,110 @@ # pylint: disable=C0103,C0111 -import unittest import json -import subprocess import mock +import unittest -import bumblebee.input -from bumblebee.input import I3BarInput -from tests.util import MockWidget, MockModule, assertPopen, assertMouseEvent, MockEpoll +import tests.mocks as mocks + +from bumblebee.input import I3BarInput, LEFT_MOUSE, RIGHT_MOUSE class TestI3BarInput(unittest.TestCase): def setUp(self): self.input = I3BarInput() self.input.need_event = True - self.anyModule = MockModule() - self.anyWidget = MockWidget("test") - self.anyModule.id = "test-module" + + self._stdin = mock.patch("bumblebee.input.sys.stdin") + self.stdin = self._stdin.start() + self._select = mock.patch("bumblebee.input.select") + self.select = self._select.start() + self.popen = mocks.MockPopen() + + self.stdin.fileno.return_value = 1 + epoll = mock.Mock() + self.select.epoll.return_value = epoll + + epoll.poll.return_value = [(self.stdin.fileno.return_value, 2)] + + self.anyModule = mock.Mock() + self.anyModule.id = mocks.rand(10) + self.anotherModule = mock.Mock() + self.anotherModule.id = mocks.rand(10) + self.anyWidget = mocks.MockWidget("some-widget") + self.anotherWidget = mocks.MockWidget("another-widget") + self.anyData = self.invalidData = "any data" + self.invalidEvent = json.dumps({"name": None, "instance": None, "button": 1}) + self.incompleteEvent = json.dumps({"button": 1}) + self.anyCommand = "this is a command with arguments" + self._called = 0 + def tearDown(self): + self._stdin.stop() + self._select.stop() + self.popen.cleanup() + def callback(self, event): self._called += 1 - @mock.patch("select.epoll") - @mock.patch("sys.stdin") - def test_basic_read_event(self, mock_input, mock_select): - mock_input.readline.return_value = "somedata" - mock_input.fileno.return_value = 1 - mock_select.return_value = MockEpoll() + def calls(self): + rv = self._called + self._called = 0 + return rv + + def test_read_event(self): + self.stdin.readline.return_value = self.anyData self.input.start() self.input.stop() - mock_input.readline.assert_any_call() + self.stdin.readline.assert_any_call() - @mock.patch("select.epoll") - @mock.patch("sys.stdin") - def test_ignore_invalid_data(self, mock_input, mock_select): - mock_select.return_value = MockEpoll() - mock_input.readline.return_value = "garbage" - self.input.start() - self.assertEquals(self.input.alive(), True) - self.assertEquals(self.input.stop(), True) - mock_input.readline.assert_any_call() + def test_ignore_invalid_input(self): + for data in [ self.invalidData, self.incompleteEvent, self.invalidEvent ]: + self.stdin.readline.return_value = data + self.input.start() + self.assertEquals(self.input.alive(), True) + self.assertEquals(self.input.stop(), True) + self.stdin.readline.assert_any_call() - @mock.patch("select.epoll") - @mock.patch("sys.stdin") - def test_ignore_invalid_event(self, mock_input, mock_select): - mock_select.return_value = MockEpoll() - mock_input.readline.return_value = json.dumps({ - "name": None, - "instance": None, - "button": 1, - }) - self.input.start() - self.assertEquals(self.input.alive(), True) - self.assertEquals(self.input.stop(), True) - mock_input.readline.assert_any_call() + def test_global_callback(self): + self.input.register_callback(None, button=LEFT_MOUSE, cmd=self.callback) + mocks.mouseEvent(button=LEFT_MOUSE, inp=self.input, stdin=self.stdin) + self.assertTrue(self.calls() > 0) - @mock.patch("select.epoll") - @mock.patch("sys.stdin") - def test_ignore_partial_event(self, mock_input, mock_select): - mock_select.return_value = MockEpoll() - self.input.register_callback(None, button=1, cmd=self.callback) - mock_input.readline.return_value = json.dumps({ - "button": 1, - }) - self.input.start() - self.assertEquals(self.input.alive(), True) - self.assertEquals(self.input.stop(), True) - mock_input.readline.assert_any_call() - - @mock.patch("select.epoll") - @mock.patch("sys.stdin") - def test_global_callback(self, mock_input, mock_select): - self.input.register_callback(None, button=1, cmd=self.callback) - assertMouseEvent(mock_input, None, mock_select, self, None, - bumblebee.input.LEFT_MOUSE, None, "someinstance") - self.assertTrue(self._called > 0) - - @mock.patch("select.epoll") - @mock.patch("sys.stdin") - def test_remove_global_callback(self, mock_input, mock_select): - self.input.register_callback(None, button=1, cmd=self.callback) + def test_remove_global_callback(self): + self.test_global_callback() self.input.deregister_callbacks(None) - assertMouseEvent(mock_input, None, mock_select, self, None, - bumblebee.input.LEFT_MOUSE, None, "someinstance") - self.assertTrue(self._called == 0) + mocks.mouseEvent(button=LEFT_MOUSE, inp=self.input, stdin=self.stdin) + self.assertTrue(self.calls() == 0) - @mock.patch("select.epoll") - @mock.patch("sys.stdin") - def test_global_callback_button_missmatch(self, mock_input, mock_select): - self.input.register_callback(self.anyModule, button=1, cmd=self.callback) - assertMouseEvent(mock_input, None, mock_select, self, None, - bumblebee.input.RIGHT_MOUSE, None, "someinstance") - self.assertTrue(self._called == 0) + def test_global_callback_wrong_button(self): + self.input.register_callback(None, button=LEFT_MOUSE, cmd=self.callback) + mocks.mouseEvent(button=RIGHT_MOUSE, inp=self.input, stdin=self.stdin) + self.assertTrue(self.calls() == 0) - @mock.patch("select.epoll") - @mock.patch("sys.stdin") - def test_module_callback(self, mock_input, mock_select): - self.input.register_callback(self.anyModule, button=1, cmd=self.callback) - assertMouseEvent(mock_input, None, mock_select, self, self.anyModule, - bumblebee.input.LEFT_MOUSE, None) - self.assertTrue(self._called > 0) + def test_module_callback(self): + self.input.register_callback(self.anyModule, button=LEFT_MOUSE, cmd=self.callback) + mocks.mouseEvent(button=LEFT_MOUSE, inp=self.input, stdin=self.stdin, module=self.anyModule) + self.assertTrue(self.calls() > 0) + mocks.mouseEvent(button=LEFT_MOUSE, inp=self.input, stdin=self.stdin, module=self.anotherModule) + self.assertTrue(self.calls() == 0) - @mock.patch("select.epoll") - @mock.patch("sys.stdin") - def test_remove_module_callback(self, mock_input, mock_select): - self.input.register_callback(self.anyModule, button=1, cmd=self.callback) + def test_remove_module_callback(self): + self.test_module_callback() self.input.deregister_callbacks(self.anyModule) - assertMouseEvent(mock_input, None, mock_select, self, None, - bumblebee.input.LEFT_MOUSE, None, self.anyWidget.id) - self.assertTrue(self._called == 0) + mocks.mouseEvent(button=LEFT_MOUSE, inp=self.input, stdin=self.stdin, module=self.anyModule) + self.assertTrue(self.calls() == 0) - @mock.patch("select.epoll") - @mock.patch("sys.stdin") - def test_widget_callback(self, mock_input, mock_select): - self.input.register_callback(self.anyWidget, button=1, cmd=self.callback) - assertMouseEvent(mock_input, None, mock_select, self, None, - bumblebee.input.LEFT_MOUSE, None, self.anyWidget.id) - self.assertTrue(self._called > 0) + def test_widget_callback(self): + self.input.register_callback(self.anyWidget, button=LEFT_MOUSE, cmd=self.callback) + mocks.mouseEvent(button=LEFT_MOUSE, inp=self.input, stdin=self.stdin, module=self.anyWidget) + self.assertTrue(self.calls() > 0) + mocks.mouseEvent(button=LEFT_MOUSE, inp=self.input, stdin=self.stdin, module=self.anotherWidget) + self.assertTrue(self.calls() == 0) + + def test_widget_cmd_callback(self): + self.input.register_callback(self.anyWidget, button=LEFT_MOUSE, cmd=self.anyCommand) + mocks.mouseEvent(button=LEFT_MOUSE, inp=self.input, stdin=self.stdin, module=self.anyWidget) + self.popen.assert_call(self.anyCommand) - @mock.patch("select.epoll") - @mock.patch("subprocess.Popen") - @mock.patch("sys.stdin") - def test_widget_cmd_callback(self, mock_input, mock_output, mock_select): - self.input.register_callback(self.anyWidget, button=1, cmd="echo") - assertMouseEvent(mock_input, mock_output, mock_select, self, None, - bumblebee.input.LEFT_MOUSE, "echo", self.anyWidget.id) # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/tests/test_i3baroutput.py b/tests/test_i3baroutput.py index f4c4cca..9b81c42 100644 --- a/tests/test_i3baroutput.py +++ b/tests/test_i3baroutput.py @@ -1,104 +1,131 @@ # pylint: disable=C0103,C0111 import json -import unittest import mock +import unittest + try: from StringIO import StringIO except ImportError: from io import StringIO +import tests.mocks as mocks + from bumblebee.output import I3BarOutput -from tests.util import MockWidget, MockTheme, MockModule class TestI3BarOutput(unittest.TestCase): def setUp(self): - self.theme = MockTheme() + self.theme = mock.Mock() + self.theme.separator_fg.return_value = "#123456" + self.theme.separator_bg.return_value = "#000000" + self.theme.separator.return_value = "" + self.theme.prefix.return_value = "" + self.theme.suffix.return_value = "" + self.theme.separator_block_width.return_value = 1 + self.theme.fg.return_value = "#ababab" + self.theme.bg.return_value = "#ababab" self.output = I3BarOutput(self.theme) + + self._stdout = mock.patch("bumblebee.output.sys.stdout", new_callable=StringIO) + self.stdout = self._stdout.start() + + self.anyWidget = mocks.MockWidget("some text") + self.anyModule = mock.Mock() + self.anyModule.id = mocks.rand(10) + self.anyModule.name = mocks.rand(10) + self.expectedStart = json.dumps({"version": 1, "click_events": True}) + "[\n" self.expectedStop = "]\n" - self.someWidget = MockWidget("foo bar baz") - self.anyModule = MockModule(None, None) - self.anyColor = "#ababab" - self.anotherColor = "#cccccc" - @mock.patch("sys.stdout", new_callable=StringIO) - def test_start(self, stdout): + self.anyColor = "#ffffff" + self.anotherColor = "#cdcdcd" + + def tearDown(self): + self._stdout.stop() + + def test_start(self): self.output.start() - self.assertEquals(self.expectedStart, stdout.getvalue()) + self.assertEquals(self.expectedStart, self.stdout.getvalue()) - @mock.patch("sys.stdout", new_callable=StringIO) - def test_stop(self, stdout): + def test_stop(self): self.output.stop() - self.assertEquals(self.expectedStop, stdout.getvalue()) + self.assertEquals(self.expectedStop, self.stdout.getvalue()) - @mock.patch("sys.stdout", new_callable=StringIO) - def test_draw_single_widget(self, stdout): - self.output.draw(self.someWidget, self.anyModule) + def test_draw_single_widget(self): + self.output.draw(self.anyWidget, self.anyModule) self.output.flush() - result = json.loads(stdout.getvalue())[0] - self.assertEquals(result["full_text"], self.someWidget.full_text()) + result = json.loads(self.stdout.getvalue())[0] + self.assertEquals(result["full_text"], self.anyWidget.full_text()) - @mock.patch("sys.stdout", new_callable=StringIO) - def test_draw_multiple_widgets(self, stdout): - for widget in [self.someWidget, self.someWidget]: - self.output.draw(widget, self.anyModule) + def test_draw_multiple_widgets(self): + for i in range(4): + self.output.draw(self.anyWidget, self.anyModule) self.output.flush() - result = json.loads(stdout.getvalue()) + result = json.loads(self.stdout.getvalue()) for res in result: - self.assertEquals(res["full_text"], self.someWidget.full_text()) + self.assertEquals(res["full_text"], self.anyWidget.full_text()) - @mock.patch("sys.stdout", new_callable=StringIO) - def test_begin(self, stdout): + def test_begin(self): self.output.begin() - self.assertEquals("", stdout.getvalue()) + self.assertEquals("", self.stdout.getvalue()) - @mock.patch("sys.stdout", new_callable=StringIO) - def test_end(self, stdout): + def test_end(self): self.output.end() - self.assertEquals(",\n", stdout.getvalue()) + self.assertEquals(",\n", self.stdout.getvalue()) - @mock.patch("sys.stdout", new_callable=StringIO) - def test_prefix(self, stdout): - self.theme.attr_prefix = " - " - self.output.draw(self.someWidget, self.anyModule) + def test_prefix(self): + self.theme.prefix.return_value = " - " + self.output.draw(self.anyWidget, self.anyModule) self.output.flush() - result = json.loads(stdout.getvalue())[0] - self.assertEquals(result["full_text"], "{}{}".format( - self.theme.prefix(self.someWidget), self.someWidget.full_text()) - ) + result = json.loads(self.stdout.getvalue())[0] + self.assertEquals(result["full_text"], " - {}".format(self.anyWidget.full_text())) - @mock.patch("sys.stdout", new_callable=StringIO) - def test_suffix(self, stdout): - self.theme.attr_suffix = " - " - self.output.draw(self.someWidget, self.anyModule) + def test_suffix(self): + self.theme.suffix.return_value = " - " + self.output.draw(self.anyWidget, self.anyModule) self.output.flush() - result = json.loads(stdout.getvalue())[0] - self.assertEquals(result["full_text"], "{}{}".format( - self.someWidget.full_text(), self.theme.suffix(self.someWidget)) - ) + result = json.loads(self.stdout.getvalue())[0] + self.assertEquals(result["full_text"], "{} - ".format(self.anyWidget.full_text())) - @mock.patch("sys.stdout", new_callable=StringIO) - def test_bothfix(self, stdout): - self.theme.attr_suffix = " - " - self.theme.attr_prefix = " * " - self.output.draw(self.someWidget, self.anyModule) + def test_bothfix(self): + self.theme.prefix.return_value = "*" + self.theme.suffix.return_value = " - " + self.output.draw(self.anyWidget, self.anyModule) self.output.flush() - result = json.loads(stdout.getvalue())[0] - self.assertEquals(result["full_text"], "{}{}{}".format( - self.theme.prefix(self.someWidget), - self.someWidget.full_text(), - self.theme.suffix(self.someWidget) - )) + result = json.loads(self.stdout.getvalue())[0] + self.assertEquals(result["full_text"], "*{} - ".format(self.anyWidget.full_text())) - @mock.patch("sys.stdout", new_callable=StringIO) - def test_colors(self, stdout): - self.theme.attr_fg = self.anyColor - self.theme.attr_bg = self.anotherColor - self.output.draw(self.someWidget, self.anyModule) + def test_colors(self): + self.theme.fg.return_value = self.anyColor + self.theme.bg.return_value = self.anotherColor + self.output.draw(self.anyWidget, self.anyModule) self.output.flush() - result = json.loads(stdout.getvalue())[0] + result = json.loads(self.stdout.getvalue())[0] self.assertEquals(result["color"], self.anyColor) self.assertEquals(result["background"], self.anotherColor) + def test_widget_link(self): + self.anyWidget.link_module(self.anyModule) + self.assertEquals(self.anyWidget._module, self.anyModule) + self.assertEquals(self.anyWidget.module, self.anyModule.name) + + def test_unlinked_widget_state(self): + state = self.anyWidget.state() + self.assertTrue(type(state) == list) + + def test_linked_widget_state(self): + self.anyWidget.link_module(self.anyModule) + for lst in [ "samplestate", ["a", "b", "c"], [] ]: + self.anyModule.state.return_value = lst + state = self.anyWidget.state() + self.assertEquals(type(state), list) + if type(lst) is not list: lst = [lst] + self.assertEquals(state, lst) + + def test_widget_fulltext(self): + self.anyWidget.full_text("some text") + self.assertEquals(self.anyWidget.full_text(), "some text") + self.anyWidget.full_text(lambda x: "callable fulltext") + self.assertEquals(self.anyWidget.full_text(), "callable fulltext") + # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/tests/test_module.py b/tests/test_module.py index 0cc95ad..cf663dc 100644 --- a/tests/test_module.py +++ b/tests/test_module.py @@ -4,14 +4,17 @@ import unittest from bumblebee.engine import Module from bumblebee.config import Config -from tests.util import MockWidget +from tests.mocks import MockWidget class TestModule(unittest.TestCase): def setUp(self): - self.widget = MockWidget("foo") + self.widgetName = "foo" + self.widget = MockWidget(self.widgetName) self.config = Config() + self.anyWidgetName = "random-widget-name" + self.noSuchModule = "this-module-does-not-exist" self.moduleWithoutWidgets = Module(engine=None, widgets=None) - self.moduleWithOneWidget = Module(engine=None, widgets=self.widget) + self.moduleWithOneWidget = Module(engine=None, widgets=self.widget, config={"config": self.config}) self.moduleWithMultipleWidgets = Module(engine=None, widgets=[self.widget, self.widget, self.widget] ) @@ -41,6 +44,40 @@ class TestModule(unittest.TestCase): for widget in self.moduleWithMultipleWidgets.widgets(): self.assertEquals(widget, self.widget) + def test_retrieve_widget_by_name(self): + widget = MockWidget(self.anyWidgetName) + widget.name = self.anyWidgetName + module = Module(engine=None, widgets=[self.widget, widget, self.widget]) + retrievedWidget = module.widget(self.anyWidgetName) + self.assertEquals(retrievedWidget, widget) + + def test_retrieve_widget_by_id(self): + widget = MockWidget(self.anyWidgetName) + widget.id = self.anyWidgetName + module = Module(engine=None, widgets=[self.widget, widget, self.widget]) + retrievedWidget = module.widget_by_id(self.anyWidgetName) + self.assertEquals(retrievedWidget, widget) + + def test_retrieve_missing_widget(self): + module = self.moduleWithMultipleWidgets + + widget = module.widget(self.noSuchModule) + self.assertEquals(widget, None) + + widget = module.widget_by_id(self.noSuchModule) + self.assertEquals(widget, None) + + def test_threshold(self): + module = self.moduleWithOneWidget + module.name = self.widgetName + + self.config.set("{}.critical".format(self.widgetName), 10.0) + self.config.set("{}.warning".format(self.widgetName), 8.0) + self.assertEquals("critical", module.threshold_state(10.1, 0, 0)) + self.assertEquals("warning", module.threshold_state(10.0, 0, 0)) + self.assertEquals(None, module.threshold_state(8.0, 0, 0)) + self.assertEquals(None, module.threshold_state(7.9, 0, 0)) + def test_parameters(self): self.assertEquals(self.anyModule.parameter(self.anyKey), self.anyValue) self.assertEquals(self.anotherModule.parameter(self.anyKey), self.anotherValue) @@ -48,3 +85,5 @@ class TestModule(unittest.TestCase): def test_default_parameters(self): self.assertEquals(self.anyModule.parameter(self.emptyKey), None) self.assertEquals(self.anyModule.parameter(self.emptyKey, self.anyValue), self.anyValue) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/tests/modules/test_modules.py b/tests/test_modules.py similarity index 58% rename from tests/modules/test_modules.py rename to tests/test_modules.py index 59db4fa..4b280a3 100644 --- a/tests/modules/test_modules.py +++ b/tests/test_modules.py @@ -1,53 +1,54 @@ # pylint: disable=C0103,C0111 +import mock import unittest import importlib -import mock + +import tests.mocks as mocks from bumblebee.engine import all_modules +from bumblebee.output import Widget from bumblebee.config import Config -from tests.util import assertWidgetAttributes, MockEngine - -class MockCommunicate(object): - def __init__(self): - self.returncode = 0 - - def communicate(self): - return (str.encode("1"), "error") class TestGenericModules(unittest.TestCase): - @mock.patch("subprocess.Popen") - def setUp(self, mock_output): - mock_output.return_value = MockCommunicate() - engine = MockEngine() + def setUp(self): + engine = mock.Mock() + engine.input = mock.Mock() config = Config() self.objects = {} + + self.popen = mocks.MockPopen() + self.popen.mock.communicate.return_value = (str.encode("1"), "error") + self.popen.mock.returncode = 0 + + self._platform = mock.patch("bumblebee.modules.kernel.platform") + self.platform = self._platform.start() + self.platform.release.return_value = "unknown linux v1" + for mod in all_modules(): - cls = importlib.import_module("bumblebee.modules.{}".format(mod["name"])) + name = "bumblebee.modules.{}".format(mod["name"]) + cls = importlib.import_module(name) self.objects[mod["name"]] = getattr(cls, "Module")(engine, {"config": config}) for widget in self.objects[mod["name"]].widgets(): self.assertEquals(widget.get("variable", None), None) - @mock.patch("subprocess.Popen") - def test_widgets(self, mock_output): - mock_output.return_value = MockCommunicate() + def tearDown(self): + self._platform.stop() + self.popen.cleanup() + + def test_widgets(self): for mod in self.objects: widgets = self.objects[mod].widgets() for widget in widgets: widget.link_module(self.objects[mod]) self.assertEquals(widget.module, mod) - assertWidgetAttributes(self, widget) + self.assertTrue(isinstance(widget, Widget)) + self.assertTrue(hasattr(widget, "full_text")) widget.set("variable", "value") self.assertEquals(widget.get("variable", None), "value") self.assertTrue(isinstance(widget.full_text(), str) or isinstance(widget.full_text(), unicode)) - @mock.patch("subprocess.Popen") - def test_update(self, mock_output): - mock_output.return_value = MockCommunicate() - rv = mock.Mock() - rv.configure_mock(**{ - "communicate.return_value": ("out", None) - }) + def test_update(self): for mod in self.objects: widgets = self.objects[mod].widgets() self.objects[mod].update(widgets) diff --git a/tests/test_store.py b/tests/test_store.py index 0b712f0..faa1efb 100644 --- a/tests/test_store.py +++ b/tests/test_store.py @@ -22,3 +22,5 @@ class TestStore(unittest.TestCase): def test_get_invalid_with_default_value(self): result = self.store.get(self.unsetKey, self.anyValue) self.assertEquals(result, self.anyValue) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/tests/test_theme.py b/tests/test_theme.py index ef356ca..747d749 100644 --- a/tests/test_theme.py +++ b/tests/test_theme.py @@ -1,21 +1,29 @@ # pylint: disable=C0103,C0111,W0703 +import mock import unittest from bumblebee.theme import Theme from bumblebee.error import ThemeLoadError -from tests.util import MockWidget +from tests.mocks import MockWidget class TestTheme(unittest.TestCase): def setUp(self): self.nonexistentThemeName = "no-such-theme" self.invalidThemeName = "test_invalid" self.validThemeName = "test" + self.validThemeSeparator = " * " self.themedWidget = MockWidget("bla") self.theme = Theme(self.validThemeName) self.cycleTheme = Theme("test_cycle") + self.anyModule = mock.Mock() self.anyWidget = MockWidget("bla") self.anotherWidget = MockWidget("blub") + self.anyModule.state.return_value = "state-default" + + self.anyWidget.link_module(self.anyModule) + self.themedWidget.link_module(self.anyModule) + data = self.theme.data() self.widgetTheme = "test-widget" self.themedWidget.module = self.widgetTheme @@ -102,14 +110,25 @@ class TestTheme(unittest.TestCase): self.assertEquals(theme.fg(self.anyWidget), data["defaults"]["fg"]) self.assertEquals(theme.bg(self.anyWidget), data["defaults"]["bg"]) - self.anyWidget.attr_state = ["critical"] + self.anyModule.state.return_value = "critical" self.assertEquals(theme.fg(self.anyWidget), data["defaults"]["critical"]["fg"]) self.assertEquals(theme.bg(self.anyWidget), data["defaults"]["critical"]["bg"]) - - self.themedWidget.attr_state = ["critical"] self.assertEquals(theme.fg(self.themedWidget), data[self.widgetTheme]["critical"]["fg"]) # if elements are missing in the state theme, they are taken from the # widget theme instead (i.e. no fallback to a more general state theme) self.assertEquals(theme.bg(self.themedWidget), data[self.widgetTheme]["bg"]) + def test_separator(self): + self.assertEquals(self.validThemeSeparator, self.theme.separator(self.anyWidget)) + + def test_list(self): + theme = self.theme + data = theme.data()[self.widgetTheme]["cycle-test"]["fg"] + self.anyModule.state.return_value = "cycle-test" + self.assertTrue(len(data) > 1) + + for idx in range(0, len(data)): + self.assertEquals(theme.fg(self.themedWidget), data[idx]) + self.assertEquals(theme.fg(self.themedWidget), data[0]) + # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/tests/test_util.py b/tests/test_util.py index b9b04c4..1988756 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -1,18 +1,27 @@ # pylint: disable=C0103,C0111 +import mock import unittest +import tests.mocks as mocks + from bumblebee.util import * class TestUtil(unittest.TestCase): + def setUp(self): + self.popen = mocks.MockPopen("bumblebee.util") + self.some_command_with_args = "sample-command -a -b -c" + self.some_utf8 = "some string".encode("utf-8") + + def tearDown(self): + self.popen.cleanup() + def test_bytefmt(self): - value = 10 - display = 10 - units = [ "B", "KiB", "MiB", "GiB" ] - for unit in units: - self.assertEquals(bytefmt(value), "{:.2f}{}".format(display, unit)) - value *= 1024 - self.assertEquals(bytefmt(value), "{:.2f}GiB".format(display*1024)) + self.assertEquals(bytefmt(10), "10.00B") + self.assertEquals(bytefmt(15*1024), "15.00KiB") + self.assertEquals(bytefmt(20*1024*1024), "20.00MiB") + self.assertEquals(bytefmt(22*1024*1024*1024), "22.00GiB") + self.assertEquals(bytefmt(35*1024*1024*1024*1024), "35840.00GiB") def test_durationfmt(self): self.assertEquals(durationfmt(00), "00:00") @@ -22,4 +31,26 @@ class TestUtil(unittest.TestCase): self.assertEquals(durationfmt(3600), "01:00:00") self.assertEquals(durationfmt(7265), "02:01:05") + def test_execute(self): + execute(self.some_command_with_args) + self.assertTrue(self.popen.mock.popen.called) + self.popen.mock.popen.assert_call(self.some_command_with_args) + self.assertTrue(self.popen.mock.communicate.called) + + def test_execute_nowait(self): + execute(self.some_command_with_args, False) + self.assertTrue(self.popen.mock.popen.called) + self.popen.mock.popen.assert_call(self.some_command_with_args) + self.assertFalse(self.popen.mock.communicate.called) + + def test_execute_utf8(self): + self.popen.mock.communicate.return_value = [ self.some_utf8, None ] + self.test_execute() + + def test_execute_error(self): + self.popen.mock.returncode = 1 + + with self.assertRaises(RuntimeError): + execute(self.some_command_with_args) + # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/tests/util.py b/tests/util.py deleted file mode 100644 index c0f244d..0000000 --- a/tests/util.py +++ /dev/null @@ -1,162 +0,0 @@ -# pylint: disable=C0103,C0111,W0613 - -import json -import shlex -import subprocess - -from bumblebee.output import Widget - -def assertWidgetAttributes(test, widget): - test.assertTrue(isinstance(widget, Widget)) - test.assertTrue(hasattr(widget, "full_text")) - -def assertPopen(output, cmd): - res = shlex.split(cmd) - output.assert_any_call(res, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT - ) - -def assertStateContains(test, module, state): - for widget in module.widgets(): - widget.link_module(module) - module.update(module.widgets()) - test.assertTrue(state in module.widgets()[0].state()) - -class MockEpoll(object): - def register(self, fileno, event): - pass - - def poll(self, timeout): - return [(1,2)] - - def unregister(self, fileno): - pass - - def close(self): - pass - -def assertMouseEvent(mock_input, mock_output, mock_select, engine, module, button, cmd, instance_id=None): - mock_input.readline.return_value = json.dumps({ - "name": module.id if module else "test", - "button": button, - "instance": instance_id - }) - mock_input.fileno.return_value = 1 - mock_select.return_value = MockEpoll() - engine.input.start() - engine.input.stop() - mock_input.readline.assert_any_call() - if cmd: - assertPopen(mock_output, cmd) - -class MockInput(object): - def __init__(self): - self._callbacks = {} - def start(self): - pass - - def stop(self): - pass - - def get_callback(self, uid): - return self._callbacks.get(uid, None) - - def register_callback(self, obj, button, cmd): - if not obj: - return - self._callbacks[obj.id] = { - "button": button, - "command": cmd, - } - -class MockEngine(object): - def __init__(self): - self.input = MockInput() - -class MockConfig(object): - def __init__(self): - self._data = {} - - def get(self, name, default): - if name in self._data: - return self._data[name] - return default - - def set(self, name, value): - self._data[name] = value - -class MockOutput(object): - def start(self): - pass - - def stop(self): - pass - - def draw(self, widget, engine, module): - engine.stop() - - def begin(self): - pass - - def flush(self): - pass - - def end(self): - pass - -class MockModule(object): - def __init__(self, engine=None, config=None): - self.id = None - -class MockWidget(Widget): - def __init__(self, text): - super(MockWidget, self).__init__(text) - self._text = text - self.module = None - self.attr_state = ["state-default"] - self.id = "none" - - def state(self): - return self.attr_state - - def update(self, widgets): - pass - - def full_text(self): - return self._text - -class MockTheme(object): - def __init__(self): - self.attr_prefix = None - self.attr_suffix = None - self.attr_fg = None - self.attr_bg = None - self.attr_separator = None - self.attr_separator_block_width = 0 - - def padding(self, widget): - return "" - - def reset(self): - pass - - def separator_block_width(self, widget): - return self.attr_separator_block_width - - def separator(self, widget): - return self.attr_separator - - def prefix(self, widget, default=None): - return self.attr_prefix - - def suffix(self, widget, default=None): - return self.attr_suffix - - def fg(self, widget): - return self.attr_fg - - def bg(self, widget): - return self.attr_bg - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/themes/test.json b/themes/test.json index 401a234..1d457a7 100644 --- a/themes/test.json +++ b/themes/test.json @@ -17,6 +17,9 @@ "bg": "#222222", "critical": { "fg": "#bababa" + }, + "cycle-test": { + "fg": [ "#000000", "#111111" ] } } }