Fuzzy assertions with cljs.test and core.match



Sometimes I need to test content of a big structure, where not all content meaningful, so I need something like fuzzy assertions. And I use core.match for that, like:

(ns example.test
  (:require [cljs.test :refer-macros [deftest is]]
            [cljs.core.match :refer-macros [match]]
            [example.core :refer [get-script]]))

(deftest test-render
  (let [script (get-script)]
    (is (match script
          [[:new [:ref _] "Canvas" []]
           [:call [:ref _] [:ref _] "getContext" [[:val "2d"]]]
           [:set [:ref _] "width" [:val 30]]
           [:set [:ref _] "height" [:val 40]]
           [:set [:ref _] "fillStyle" [:val "red"]]] true
          _ false))))

But it’s redundant, and failure messages are a bit ugly and it doesn’t even show content of not matched structure:

That can be easily fixed, because cljs.test is very extendable. So we just need to create macros for simpler matching and implement assert-expr method for it:

(ns example.test-utils
  (:require [cljs.core.match :refer [match]]
            [cljs.test :refer [assert-expr]]))

(defmacro match?
  [x pattern]
  `(match ~x
     ~pattern true
     _# false))

(defmethod assert-expr 'match? [_ msg form]
  (let [[_ x pattern] form]
    `(if ~form
       (cljs.test/do-report {:type :pass
                             :message ~msg
                             :expected '~form
                             :actual nil})
       (cljs.test/do-report {:type :fail
                             :message ~msg
                             :expected '~pattern
                             :actual ~x}))))

Then update test:

(ns example.test
  (:require [cljs.test :refer-macros [deftest is]]
            [example.test :refer-macros [match?]]
            [example.core :refer [get-script]]))

(deftest test-render
  (let [script (get-script)]
    (is (match? script
          [[:new [:ref _] "Canvas" []]
           [:call [:ref _] [:ref _] "getContext" [[:val "2d"]]]
           [:set [:ref _] "width" [:val 30]]
           [:set [:ref _] "height" [:val 40]]
           [:set [:ref _] "fillStyle" [:val "red"]]]))))

After that failure message will be much nicer:

Bonnie Eisenman: Learning React Native



book cover white Lately I’m working a lot with React, and mobile development looks interesting to me. So I decided to read something about React Native and chosen Learning React Native by Bonnie Eisenman. It’s a short book, mostly in tutorial format. Explains differences with React and platform specific things.

But the book have a few downsides, there’s too much screenshots of XCode and app stores, book mostly written for OS X users and contains not so much info for Android development.

Sums up everything, it seems to be a nice introductory book.

Better ClojureScript tests output with Chrome and cljs-devtools



For testing ClojureScript code I use cljs.test with figwheel. It’s very cool, tests runs when every .cljs file changed, but frequently output in DevTools is a mess. And it would be nice to have highlighted code in expected/actual part.

Then I found a nice lib – cljs-devtools. For using it you need to add it to your project and configure Chrome DevTools.

It’s nice, but it doesn’t work with print/println, only with console.log. And only when clojure data structures passed to console.log. cljs.test uses print and formatters for printing expected/actual. So we need to hack cljs.test a bit. Assume your test runner looks like:

(ns ^:figwheel-always rerenderer.test
  (:require [cljs.test :refer-macros [run-all-tests]]))

(enable-console-print!)
(run-all-tests #"rerenderer\..*-test")

And we need to redefine cljs/test.print-comparison:

(ns ^:figwheel-always rerenderer.test
  (:require [cljs.test :refer-macros [run-all-tests] :as test]
            [devtools.core :as devtools]))

(enable-console-print!)
(devtools/install!)

(defn print-comparison
  [{:keys [expected actual]}]
  (.log js/console "expected:" expected)
  (.log js/console "  actual:" actual))

(with-redefs [test/print-comparison print-comparison]
  (run-all-tests #"rerenderer\..*-test"))

And that’s all, example output:

Andreas Oehlke: Learning LibGDX Game Development



book cover I’m a bit interested in game development and LibGDX seems to be a good framework, so I read Learning LibGDX Game Development by Andreas Oehlke. I’ve planned to read about best practices, edge cases and how it works inside. And it’s not that kind of book. It’s just an entry level tutorial with a lot of printed code.

But the author described development of a little game touching almost all LibGDX parts. So maybe it’s a nice tutorial for newcomers.

Michael L. Scott: Programming Language Pragmatics



book cover Yesterday I’ve finished reading Programming Language Pragmatics by Michael L. Scott and it’s a big book with a lot of exercises (I’ve tried to do most of them) and with a ton of additional material on PLP CD. This book covers most of PL related concepts from grammar to concurrency and memory management. Also it explains why something implemented the way it implemented in exists programming languages. And has information about ton of languages, from Algol to Haskell.

But some chapters is outdated, for example chapter about in-browser scripting. And some chapters covers not all aspects, it’s like 50 pages about mutexes and locks and only 2 pages about message passing in chapter about concurrency.

Despite this, I guess it’s must read book for software developers.

Talking with Hubot through Google Glass



For home automation I use Hubot, it’s something like a framework for creating chat bots, it’s very easy to use and rules for it can be written in CoffeeScript, like:

robot.hear /pause move/i, (res) ->
  exec 'player pause'
  res.send 'Sure!'

So I planned to create an app for phone, which would allow me to say “Ok Google, Hubot, next song”, or something similar. But it’s not possible, Android API not allow to create custom voice actions like “Hubot”, only commands from predefined list allowed.

But I remembered that I have useless Google Glass, which GDK API allows to create custom “Ok Glass” commands, like “Ok Glass, Hubot, next song”, at least in development mode with:

<uses-permission android:name="com.google.android.glass.permission.DEVELOPMENT" />

First of all I created Hubot adapter which support something like polling with simple API:

  • POST /polling/subscribe/ with {} respond s{user: user} – subscribe to polling;
  • POST /polling/message/ with {user: user, text: message-text} responds {ok: ok} – send message to bot;
  • GET /polling/response/:user/ responds {responses: [response]} – get unread bot responses.

It’s not so interesting, written in CoffeeScript (Hubot requires it), code is very simple.

For Glass part I’ve used Kotlin with a few nice libraries:

And it’s very great combination, with them making http request is much nicer than with java and just DefaultHttpClient, like:

Fuel.post("$url/polling/message/")
    .body(jsonObject("user" to user, "text" to text).toString())
    .header("Content-Type" to "application/json")
    .jsonPromise()
    .success { info("Message $text sent") }
    .fail { warn("Can't send message $text because $it") }

Just promises in comparison with Clojure core.async and Scala Akka it’s a bit way back, it’s like writing in pre ES7 JavaScript. So why Kotlin? It’s simpler to use on Android, struggling with dex errors with Scala isn’t fun. Performance is similar to apps written in Java, sometimes startup time of Clojure apps is annoying. And there’s far less magic then in Scala scaloid and Clojure Neko.

In action:

Sources on github.

Import python modules straight from github



In Go we have ability to import modules from github, like:

import "github.com/parnurzeal/gorequest"

It’s a bit controversial feature, but sometimes it’s useful. And I was interested, is it possible to implement something like that in python. TLDR it’s possible with import_from_github_com package:

from github_com.kennethreitz import requests

assert requests.get('https://github.com').status_code == 200

So, how it works, according to PEP-0302 we have special sys.meta_path with importer objects and every importer should implement finder protocol with find_module(module_name: str, package_path: [str]) -> Loader|None. Now we need to implement finder that handles modules, which path starts with github_com, like:

class GithubComFinder:
    def find_module(self, module_name, package_path):
        if module_name.startswith('github_com'):
            return GithubComLoader()
            
sys.meta_path.append(GithubComFinder())

And now we need GithubComLoader that implements loader protocol with load_module(fullname: str) -> None, I’ll skip private methods of the loader here, they’re straightforward and not interesting in context of the article:

class GithubComLoader:
    def load_module(self, fullname):
        if self._is_repository_path(fullname):
            self._install_module(fullname)

        if self._is_intermediate_path(fullname):
            module = IntermediateModule(fullname)
        else:
            module = self._import_module(fullname)

        sys.modules[fullname] = module

So what’s IntermediateModule, it’s a dummy module/package for paths like github_com.nvbn, it’s used only in intermediate steps and shouldn’t be used by end user. Installation happens in _install_module method, it just calls pip with git url, like:

import pip

pip.main(['install', 'git+https://github.com/kennethreitz/requests'])

All looks very simple, let’s try it in action:

>>> from github_com.kennethreitz import requests
Collecting git+https://github.com/kennethreitz/requests
  Cloning https://github.com/kennethreitz/requests to /tmp/pip-8yyvh7kr-build
Installing collected packages: requests
  Running setup.py install for requests
Successfully installed requests-2.9.1
>>> requests.get('https://github.com').status_code
200

Sources on github.

Three months in Bali, Indonesia



Rice field nearby my house

From September to December I was in Indonesia, most of the time I was living in Ubud (Bali), but also was in Jakarta, Kuta (Bali) and Gili Trawangan.

I was entering Indonesia by VOA, it allowed me to be in the country for 30 days. So every month I was going to a third country, visited Kuala Lumpur (Malaysia), Bangkok (Thailand) and Singapore.

So in Ubud I was renting part of a house with kitchen, A/C, hot water, pool (shared between 6 houses) and 8 MBit/s internet. With all bills it costs 3000000 IDR ($216). The house was beside nice rice fields, but a bit far from shops, beaches and attractions. So I was renting motorbike (Honda Scoopy) for 600000 IDR ($43).

Back to internet, it worked well in dry season, but very bed in wet season. It wasn’t working for an hour after every thunderstorm, and in some days it was like five thunderstorms. So as a backup I’ve used mobile internet by Telkomsel simPATI and paid 160000 IDR ($11) for 6GB. I’ve used 3G, but heard that now LTE available.

In Bali it’s cheaper to buy prepared food on night markets. It costs 150000-200000 IDR ($10-15) for week of eating three times a days local food like nasi/mi goreng, satay and etc. But I guess it’s not healthy, so mostly I cooked by myself from ingredients from supermarket. So with every day meat/fish/seafood it was 300000-500000 IDR ($20-35) for week.

Alcohol is a bit expensive, like 20000-30000 IDR ($1.5-2) for a can of local beer and 70000-120000 IDR ($5-8) for a bottle of local rice vodka (arac).

Tons of articles already written about Bali’s attractions, so I just mention that some beaches, waterfalls and temples are quite nice here.

How The Fuck works



Not so long ago I introduced an useful app The Fuck that fixes the previous console command. It was downloaded thousands times, got tons of stars on github, had tens of great contributors. And it’s interesting inside.

Also about a week ago I discussed about The Architecture of Open Source Applications books. And now I think it’ll be cool to write something like a chapter in the book, but about The Fuck.

Pipeline

The simplest abstraction for describing the app is a pipeline, from the user side it looks like just:

graph LR A[Something goes wrong]-->B[fuck] B-->C[All cool]

It’s that simple because fuck (or whatever user uses) is an alias, it does some magic for getting the broken command, executing fixed command and updating the history. For example for zsh it looks like:

TF_ALIAS=fuck alias fuck='eval $(thefuck $(fc -ln -1 | tail -n 1)); fc -R'

Back to pipeline, for thefuck that runs inside the alias it’ll be:

graph LR A[Broken command]-->B[thefuck] B-->C[Fixed command]

And all interesting stuff happens inside of thefuck:

graph TB A[Broken command]-->B[Matched rules] B-->C[Corrected commands] C-->|User selects one|D[Fixed command]

Most significant part here is matching rules, rule is a special modules with two functions:

  • match(command: Command) -> bool – should return True when rule matched;
  • get_new_command(command: Command) -> str|list[str] – should return fixed command or list of fixed commands.

I guess the app is cool only because of the rules. And it’s very simple to write your own and now 75 rules available, written mostly by third party contributors.

Command is a special data structure that works almost like namedtuple Command(script: str, stdout: str, stderr: str) where script is a shell-agnostic version of broken command.

Shell specific

All shells have different ways to describe aliases, different syntax (like and instead of && in fish) and different ways to work with history. And even it depends on shell configs (.bashrc, .zshrc, etc). For avoiding all this stuff a special shells module converts shell specific command to sh compatible version, expands aliases and environment variables.

So for obtaining mentioned in the previous section Command instance we using special shells.from_shell function, run result in sh, and obtain stdout and stderr:

graph TB A[Broken command]-->|from_shell|B[Shell agnostic command] B-->|Run in sh|C[Command instance]

And also we making some similar step with fixed command – convert shell agnostic command to shell specific with shells.to_shell.

Settings

The Fuck is very configurable app, user can enable/disable rules, configure ui, set rules specific options and etc. As a config app uses special ~/.thefuck/settings.py module and environment variables:

graph TB A[Default settings]-->B[Updated from settings.py] B-->C[Updated from env]

Originally settings object was passed to every place where it was needed as an argument, it was cool and testable, but too much boilerplate. Now it’s a singleton and works like django.conf.settings (thefuck.conf.settings).

UI

UI part of The Fuck is very simple, it allows to chose from variants of corrected commands with arrows, approve selection with Enter or dismiss it with Ctrl+C.

Downfall here is that there’s no function in Python standard library for reading key on non-windows and without curses. And we can’t use curses here because of alias specifics. But it’s easy to write clone of windows-specific msvrt.getch:

import tty
import termios


def getch():
    fd = sys.stdin.fileno()
    old = termios.tcgetattr(fd)
    try:
        tty.setraw(fd)
        ch = sys.stdin.read(1)
        if ch == '\x03':  # For compatibility with msvcrt.getch
            raise KeyboardInterrupt
        return ch
    finally:
        termios.tcsetattr(fd, termios.TCSADRAIN, old)

Also UI requires properly sorted list of corrected commands, so all rules should be matched before and it can took a long time. But with simple heuristic it works well, first of all we match rules in order of it’s priority. So the first corrected command returned by the first matched rule is definitely the command with max priority. And app matches other rules only when user presses arrow keys for selecting another. So for most use cases it work’s fast.

In wide

If we look to the app in wide, it’s very simple:

graph TB A[Controller]-->E[Settings] A-->B[Shells] A-->C[Corrector] C-->D[Rules] C-->E D-->E A-->F[UI]

Where controller is an entry point, that used when user use thefuck broken-command. It initialises settings, prepares command from/to shell with shells, gets corrected commands from corrector and selects one with UI.

Corrector matches all enabled rules against current command and returns all available corrected variants.

About UI, settings and rules you can read above.

Testing

Tests is one of the most important parts of any software project, without them it’ll fall apart on every change. For unit tests here’s used pytest. Because of rules there’s a lot of tests for matching and checking corrected command, so parametrized tests is very useful, typical test looks like:

import pytest
from thefuck.rules.cd_mkdir import match, get_new_command
from tests.utils import Command


@pytest.mark.parametrize('command', [
    Command(script='cd foo', stderr='cd: foo: No such file or directory'),
    Command(script='cd foo/bar/baz',
            stderr='cd: foo: No such file or directory'),
    Command(script='cd foo/bar/baz', stderr='cd: can\'t cd to foo/bar/baz')])
def test_match(command):
    assert match(command)

Also The Fuck works with various amount of shells and every shell requires specific aliases. And for testing that all works we need functional tests, there’s used my pytest-docker-pexpect, that run’s special scenarios with every supported shell inside docker containers.

Distribution

The most problematic part of The Fuck is installation of it by users. The app distributed with pip and we had a few problems:

  • some dependencies on some platforms needs python headers (python-dev), so we need to tell users manually install it;
  • pip doesn’t support post-install hooks, so users need to manually configure an alias;
  • some users uses non-supported python versions, only 2.7 and 3.3+ supported;
  • some old versions of pip doesn’t install any dependency at all;
  • some versions of pip ignores python version dependent dependencies, we need pathlib only for python older than 3.4;
  • that’s funny, but someone was pissed off because of the name and tried to remove package from pypi.

Most of this problems was fixed by using special install script, it uses pip inside, but prepares system before installation and configures an alias after.

py.test plugin for functional testing with Docker



It’s very useful to run functional tests in a clean environment, like a fresh Docker container, and I wrote about this before, and now it was formalized in a simple py.test plugin — pytest-docker-pexpect.

It provides few useful fixtures:

  • spawnupexpect.spawnu object attached to a container, it can be used to interact with apps inside the container, read more;
  • TIMEOUT – a special object, that can be used in assertions those checks output;
  • run_without_docker – indicates that tests running without Docker, when py.test called with --run-without-docker.

And some marks:

  • skip_without_docker – skips test when without Docker;
  • once_without_docker – runs parametrized test only with a first set of params when without Docker.

It’s easier to show it in examples. So, first of all, just test some app --version argument inside an Ubuntu container:

import pytest


@pytest.fixture
def ubuntu(spawnu):
    # Get `spawnu` attached to ubuntu container with installed python and
    # where bash ran
    proc = spawnu(u'example/ubuntu',
                  u'''FROM ubuntu:latest
                      RUN apt-get update
                      RUN apt-get install python python-dev python-pip''',
                  u'bash')
    # Sources root is available in `/src`
    proc.sendline(u'pip install /src')
    return proc


def test_version(ubuntu, TIMEOUT):
    ubuntu.sendline(u'app --version')
    # Asserts that `The App 2.9.1` came before timeout,
    # when timeout came first, `expect` returns 0, when app version - 1
    assert ubuntu.expect([TIMEOUT, u'The App 2.9.1'])

Looks simple. But sometimes we need to run tests in different environments, for example — with different Python versions. It can be easily done by just changing ubuntu fixture:

@pytest.fixture(params=[2, 3])
def ubuntu(request, spawnu):
    python_version = request.param
    # Get `spawnu` attached to ubuntu container with installed python and
    # where bash ran
    dockerfile = u'''
        FROM ubuntu:latest
        RUN apt-get update
        RUN apt-get install python{version} python{version}-dev python{version}-pip
    '''.format(version=python_version)
    proc = spawnu(u'example/ubuntu', dockerfile, u'bash')
    # Your source root is available in `/src`
    proc.sendline(u'pip{} install /src'.format(python_version))
    return proc

And sometimes we need to run tests in Docker-less environment, for example — in Travis CI container-based infrastructure. So here’s where --run-without-docker argument comes handy. But we don’t need to run tests for more than one environment in a single Travis CI run, and we don’t need to make some installation steps. So there’s place for once_without_docker mark and run_without_docker fixture, test with them will be:

import pytest


@pytest.fixture(params=[2, 3])
def ubuntu(request, spawnu, run_without_docker):
    python_version = request.param
    # Get `spawnu` attached to ubuntu container with installed python and
    # where bash ran
    dockerfile = u'''
        FROM ubuntu:latest
        RUN apt-get update
        RUN apt-get install python{version} python{version}-dev python{version}-pip
    '''.format(version=python_version)
    proc = spawnu(u'example/ubuntu', dockerfile, u'bash')
    # It's already installed if we run without Docker:
    if not run_without_docker:
        # Your source root is available in `/src`
        proc.sendline(u'pip{} install /src'.format(python_version))
    return proc


@pytest.mark.once_without_docker
def test_version(ubuntu, TIMEOUT):
    ubuntu.sendline(u'app --version')
    # Asserts that `The App 2.9.1` came before timeout,
    # when timeout came first, `expect` returns 0, when app version - 1
    assert ubuntu.expect([TIMEOUT, u'The App 2.9.1'])

Another often requirement — skip some tests without docker, some destructive tests. It can be done with skip_without_docker mark:

@pytest.mark.skip_without_docker
def test_broke_config(ubuntu, TIMEOUT):
    ubuntu.sendline(u'{invalid} > ~/.app/config.json')
    ubuntu.sendline(u'app')
    assert ubuntu.expect([TIMEOUT, u'Config was broken!'])

Source code of the plugin.