kelly-mclaughlin.com

Building an Erlang Project With Mix

| Comments

What in the world is Mix? Why would anyone want to do this?

These are the sorts of questions that any readers of this post may be asking themselves based on the title. The answer to the former is that Mix is the build tool of the Elixir ecosystem. For me the answer to the second question is simply curiosity and this is a brief summary of my experience following that curiosity.

As an Erlang developer for the last five years I admit to having paid very little attention to Elixir until very recently. The inspiration to actually take a closer look came from listening to an interview with José Valim on an episode of the Functional Geekery podcast. There were several aspects of Elixir discussed that piqued my interest and one of them was the build tool known as Mix.

Mix provides several features that are appealing having experienced a lot of frustration with rebar, the most popular build tool for Erlang projects. Most of the frustration with rebar comes from the dependency handling and so it was very interesting to see what Mix offered in this area in particular. Some of the features I found notable were:

  • Built-in dependency locking behavior to aid in repeatable builds.
  • Ability to resolve a dependency conflict in the top level configuration by explicitly overriding any other version of the same dependency encountered in the dependency tree.
  • The concept of environments (dev, test, and prod are the defaults) to facilitate things like omitting test dependencies from the development or production builds.
  • An umbrella mode for creating new projects that do not directly contain source code, but are compositions of other applications and dependencies.
  • Ability to specify binary packages from the Hex package manager as dependencies in addition to git repositories.

In Erlang projects that use rebar some of these things can be accomplished with plugins or various other workarounds, but I like that they are baked into Mix. I suspect many of these features will be part of rebar3, but it is still in the development stages whereas Mix can be used today. Another benefit from using Mix is the possibility for people to contribute to a project using either Erlang or Elixir. This certainly will not be applicable to all projects or desired by some, but in general I do count greater flexibility as a benefit.

Specifying a Mix configuration

After hearing the description of Mix on the podcast as mentioned previously I decided to play around with it a bit. I was very interested in the fact that Mix could be used to build regular Erlang projects so I decided to test out what it would take to actually make this happen for a real project. I have quite a bit of familiarity with Basho’s riak_cs project so I decided to use it as my test project.

First here is the rebar.config file from riak_cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
{sub_dirs, ["rel"]}.

{require_otp_vsn, "R15|R16"}.

{cover_enabled, true}.

%% EDoc options
{edoc_opts, [preprocess]}.

{lib_dirs, ["deps", "apps"]}.

{erl_opts, [debug_info, warnings_as_errors, {parse_transform, lager_transform}]}.

{xref_checks, []}.
{xref_queries,
 [{"(XC - UC) || (XU - X - B - \"(^riak$|^riak_cs_dummy_reader$|^riak_core_bucket$|^app_helper$|^riakc_pb_socket_fake$|^riak_object$|^riak_repl_pb_api$|^riak_cs_multibag$)\" : Mod)", []}]}.
{xref_queries_ee,
 [{"(XC - UC) || (XU - X - B - \"(^riak$|^riak_cs_dummy_reader$|^riak_core_bucket$|^app_helper$|^riakc_pb_socket_fake$|^riak_object$)\" : Mod)", []}]}.

{reset_after_eunit, true}.

{plugin_dir, ".plugins"}.
{plugins, [rebar_test_plugin, rebar_lock_deps_plugin]}.

{client_test, [
               {test_paths, ["client_tests/erlang"]},
               {test_output, ".client_test"}
              ]}.
{int_test, [
            {test_paths, ["int_test"]},
            {test_output, ".int_test"}
           ]}.
{riak_test, [
             {test_paths, ["riak_test/tests", "riak_test/src",
                           "deps/riak_cs_multibag/riak_test/tests",
                           "deps/riak_cs_multibag/riak_test/src"]},
             {test_output, "riak_test/ebin"}
            ]}.

{deps, [
        {node_package, "1.3.8", {git, "git://github.com/basho/node_package", {tag, "1.3.8"}}},
        {getopt, ".*", {git, "git://github.com/jcomellas/getopt.git", {tag, "v0.4.3"}}},
        {webmachine, ".*", {git, "git://github.com/basho/webmachine", {tag, "1.10.3"}}},
        {riakc, ".*", {git, "git://github.com/basho/riak-erlang-client", {tag, "1.4.2"}}},
        {lager, ".*", {git, "git://github.com/basho/lager", {tag, "2.0.3"}}},
        {lager_syslog, ".*", {git, "git://github.com/basho/lager_syslog", {tag, "2.0.3"}}},
        {eper, ".*", {git, "git://github.com/basho/eper.git", "0.78"}},
        {druuid, ".*", {git, "git://github.com/kellymclaughlin/druuid.git", {tag, "0.2"}}},
        {velvet, "1.3.*", {git, "git://github.com/basho/velvet", "4bb0fd664ff065c4082ca8dd2e0683e920537d15"}},
        {poolboy, "0.8.*", {git, "git://github.com/basho/poolboy", "0.8.1p2"}},
        {folsom, ".*", {git, "git://github.com/boundary/folsom", {tag, "0.8.1"}}},
        {cluster_info, ".*", {git, "git://github.com/basho/cluster_info", {tag, "1.2.4"}}},
        {xmerl, ".*", {git, "git://github.com/shino/xmerl", "b35bcb05abaf27f183cfc3d85d8bffdde0f59325"}},
        {erlcloud, ".*", {git, "git://github.com/basho/erlcloud.git", {tag, "0.4.4"}}},
        {rebar_lock_deps_plugin, ".*", {git, "git://github.com/seth/rebar_lock_deps_plugin.git", {tag, "3.1.0"}}}
       ]}.

{deps_ee, [
           {riak_repl_pb_api,".*",{git,"git@github.com:basho/riak_repl_pb_api.git", {tag, "0.2.5"}}},
           {riak_cs_multibag,".*",{git,"git@github.com:basho/riak_cs_multibag.git", {branch, "master"}}}
          ]}.

Now here is the mostly equivalent translation to a mix.exs file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
defmodule CS.Mixfile do
  use Mix.Project

  def project do
    [app: :riak_cs,
     version: "1.5.2",
     compilers: [:erlang, :app],
     erlc_options: [{:parse_transform, :lager_transform}],
     deps: deps]
  end

  def application do
    [applications: [:kernel,
                    :stdlib,
                    :inets,
                    :crypto,
                    :mochiweb,
                    :webmachine,
                    :poolboy,
                    :lager,
                    :cluster_info,
                    :folsom]]
  end

  defp deps do
    [{:node_package, github: "basho/node_package", tag: "1.3.8", compile: "../../rebar compile"},
     {:getopt, github: "jcomellas/getopt", tag: "v0.4.3"},
     {:ibrowse, github: "cmullaparthi/ibrowse", tag: "v4.0.1", override: true},
     {:webmachine, github: "basho/webmachine", tag: "1.10.6"},
     {:riakc, github: "basho/riak-erlang-client", tag: "1.4.2"},
     {:lager, github: "basho/lager", tag: "2.0.3", override: true},
     {:lager_syslog, github: "basho/lager_syslog", tag: "2.0.3"},
     {:eper, gippthub: "basho/eper", tag: "0.78"},
     {:meck, github: "eproxus/meck", tag: "0.8.2", override: true},
     {:druuid, github: "kellymclaughlin/druuid", tag: "0.3"},
     {:velvet, github: "basho/velvet", ref: "4bb0fd664ff065c4082ca8dd2e0683e920537d15"},
     {:poolboy, github: "devinus/poolboy", tag: "1.4.1"},
     {:folsom, github: "boundary/folsom", tag: "0.8.1"},
     {:cluster_info, github: "basho/cluster_info", tag: "1.2.4"},
     {:erlcloud, github: "basho/erlcloud", tag: "0.4.4", only: :test}
    ]
  end
end

The two files are very similar, but the thing that sticks out most is the cleaniness of the dependency specification in the Mix file versus the rebar configuration. The github shorthand is very nice since it is the most common home for Erlang projects. Also, notice that Mix gathers the information to generate the project’s .app file from information in the mix.exs file rather than an app.src file.

The dependencies that specify override: true are ones that had version conflicts and I was able to select the correct version explicitly at the top level. This is also the reason that the set of dependencies specified differs slightly between the rebar and Mix configurations.

In order to build the project I also had to specify a basic mix.exs file in the apps/riak_cs directory. Here is the listing of that file:

1
2
3
4
5
6
7
8
9
Code.require_file "mix.exs", "../.."

defmodule CS.App.Mixfile do
  use Mix.Project

  def project do
    []
  end
end

Testing it out

Elixir and Mix require at least Erlang 17 so I did have to alter the versions some of the dependencies in order to get a working build due to some version-related build errors and for some of the Basho dependencies I had to manually edit the rebar.config file to allow using version 17 or disable warnings_as_errors (also related to building with Erlang 17). Beyond that everything built as expected with very minimal overall effort.

Here are the steps to follow to build the project:

  1. mix deps.get
  2. Edit deps/velvet/rebar.config in the following manner:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
    diff --git a/rebar.config b/rebar.config
    index 9a03432..56d7170 100644
    --- a/rebar.config
    +++ b/rebar.config
    @@ -1,10 +1,10 @@
    -{require_otp_vsn, "R14B0[234]|R15|R16"}.
    +{require_otp_vsn, "R14B0[234]|R15|R16|17"}.
    
     {cover_enabled, true}.
    
     {lib_dirs, ["apps"]}.
    
    -{erl_opts, [debug_info, warnings_as_errors, {parse_transform, lager_transform}]}.
    +{erl_opts, [debug_info, {parse_transform, lager_transform}]}.
    
    {reset_after_eunit, true}.
  3. Edit deps/riakc/rebar.config in the following manner:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    diff --git a/rebar.config b/rebar.config
    index f09a602..9765dc3 100644
    --- a/rebar.config
    +++ b/rebar.config
    @@ -1,6 +1,6 @@
     {cover_enabled, true}.
     {eunit_opts, [verbose]}.
    -{erl_opts, [warnings_as_errors, debug_info]}.
    +{erl_opts, [debug_info]}.
     {deps, [
             {riak_pb, ".*", {git, "git://github.com/basho/riak_pb", {tag, "1.4.4.0"}}}
            ]}.
  4. mix deps.compile && mix compile

That is all there is to it. The results should be in the local _build directory.

Conclusion and further explorations

Mix appears to be a well-implemented, flexible build tool for building Elixir or Erlang projects. From an Erlang developer’s perspective, it definitely addresses some pain points that frequently arise with dependency handling with rebar.

I also like that the build tool is a core part of the Elixir ecosystem. Many Erlang projects include a copy of the rebar executable as part of the git repository because rebar is not the official Erlang build tool and is not distributed as part of the language installation. This, in my view, is a problematic and sloppy solution so Elixir definitely improves upon that with Mix.

I have not explored how Elixir/Mix handle interaction with reltool for creating releases yet. An interesting next step will be to explore this more and attempt to get an actual release of the project created. One missing thing from the rebar.config in the example of riak_cs is the handling of the rebar plugin for executing customized testing functions. Translating the plugin actions into Mix tasks would be another interesting learning experience.

Platform-Specific Build Options With Rebar

| Comments

Several months back someone reported an issue when attempting to use druuid as a dependency in an Elixir project. At the time I was not able to look into it, but recently I circled back to dig deeper into the problem (described in more detail here) and in the process I stumbled across some useful rebar configuration options for projects that have platform-specific build requirements. I use rebar quite a bit, but I was not aware that these options existed so I wrote this in case other rebar users find themselves in a similar state of ignorance.

Druuid needs to differentiate between BSD and non-BSD systems during the compile and clean operations. This is due to a conflict with the OS-provided UUID library on BSD systems. The original method used to achieve this was rebar’s dynamic configuration capability. A rebar.config.script file was used to check the system architecture information using rebar_utils:is_arch/1 and modify the rebar configuration appropriately based on the results (relevant github issue). Here is the full listing of the rebar.config.script file for reference.

This solution certainly works, but it would be great if there was a way to use standard rebar configuration options to solve the problem and even better if anyone who wants to use the library, be it in an Erlang project or an Elixir project, could easily do so.

Fortunately such configuration capabilities already exist in rebar. Druuid uses four rebar configuration directives to tailor the build environment and steps based on the platform. These are as follows: port_specs, port_env, pre_hooks, post_hooks. Each option is specified in the rebar.config file as a pair where the first element is the option name and the second element is a list of tuples.

For example, here is what a port_env entry might look like:

1
2
3
4
{port_env, [{"CFLAGS", "$CFLAGS -fPIC"},
            {"DRV_CFLAGS", "$DRV_CFLAGS -Werror -I c_src/uuid-1.6.2"},
            {"DRV_LDFLAGS", "$DRV_LDFLAGS c_src/uuid-1.6.2/.libs/libuuid.a"}
]}.

It turns out that the value tuples can be a pair as shown in the above example or they can be triples where the first element is a regular expression that is applied as a filter to the platform information. Let’s see an example of that:

1
2
3
4
5
6
{port_env, [{"CFLAGS", "$CFLAGS -fPIC"},
            {"(bsd)", "DRV_CFLAGS", "$DRV_CFLAGS -Werror"},
            {"(bsd)", "DRV_LDFLAGS", "$DRV_LDFLAGS"},
            {"^(?s)((?!bsd).)*$", "DRV_CFLAGS", "$DRV_CFLAGS -Werror -I c_src/uuid-1.6.2"},
            {"^(?s)((?!bsd).)*$", "DRV_LDFLAGS", "$DRV_LDFLAGS c_src/uuid-1.6.2/.libs/libuuid.a"}
]}.

The CFLAGS value is applied to all platforms, but DRV_CFLAGS and DRV_LDFLAGS are selected based on whether the platform information does or does not contain the string bsd. This is very handy and can also be used for all of the other configuration options used by druuid. Let’s see the final version of the rebar.config file that translates all of the behavior previously contained in the rebar.config.script file:

1
2
3
4
5
6
7
8
9
{port_specs, [{"priv/druuid.so", ["c_src/*.c"]}]}.
{port_env, [{"CFLAGS", "$CFLAGS -fPIC"},
            {"(bsd)", "DRV_CFLAGS", "$DRV_CFLAGS -Werror"},
            {"(bsd)", "DRV_LDFLAGS", "$DRV_LDFLAGS"},
            {"^(?s)((?!bsd).)*$", "DRV_CFLAGS", "$DRV_CFLAGS -Werror -I c_src/uuid-1.6.2"},
            {"^(?s)((?!bsd).)*$", "DRV_LDFLAGS", "$DRV_LDFLAGS c_src/uuid-1.6.2/.libs/libuuid.a"}
]}.
{pre_hooks, [{"^(?s)((?!bsd).)*$", compile, "c_src/build_deps.sh"}]}.
{post_hooks, [{"^(?s)((?!bsd).)*$", clean, "c_src/build_deps.sh clean"}]}.

This is much nicer than resorting to using rebar’s dynamic configuration and as a bonus it works much better with Elixir’s build tool, Mix. If you want to explore how the regular expressions are treated inside rebar, look here for the hooks, here for the port_specs, and here for the port_env for the port options.

I did not find any mention of these options in the rebar wiki. I did find some examples of the port options in the output of rebar help compile, but hopefully this post sheds some further light on their intended purpose. Also note that the information in this post is based on the 2.5.1 tag of rebar. Later tags or the work on rebar3 may make some of this information obsolete.

Setting Up the Emacs Daemon on OS X

| Comments

Warning: I only use terminal emacs so I have no idea how well this will work otherwise.

Install emacs 23 or later.

Here is what I use to get the very latest emacs goodies:

1
sudo brew install emacs --cocoa --use-git-head --HEAD

Some people have had trouble with the --cocoa flag so you may find it convenient to omit that if you do not need the Cocoa version. Also if you do not want the bleeding edge version, then omit the --use-git-head --HEAD options.

Create a plist file and move it to ~/Library/LaunchAgents

Here is what my file looks like:

<?xml version="1.0" encoding="UTF-8"?>
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
  <plist version="1.0">
    <dict>
      <key>Label</key>
      <string>gnu.emacs.daemon</string>
      <key>ProgramArguments</key>
      <array>
        <string>/usr/local/Cellar/emacs/HEAD/Emacs.app/Contents/MacOS/Emacs</string>
        <string>--daemon</string>
      </array>
      <key>RunAtLoad</key>
      <true/>
      <key>ServiceDescription</key>
      <string>Gnu Emacs Daemon</string>
      <key>UserName</key>
      <string>kelly</string>
    </dict>
  </plist>

Update the path the emacs and change the username to your OS X user name.

Start the daemon

1
2
launchctl load gnu.emacs.daemon
launchctl start gnu.emacs.daemon

Edit files with emacsclient instead of emacs

Instead of opening a file with emacs <filename> instead use emacsclient -nw <filename>.

I created a handy alias in my .bashrc file.

1
alias e="emacsclient -nw"

Stopping the daemon

From within emacs, use the save-buffers-kill-emacs command.

Outside of emacs:

1
emacsclient -e '(save-buffers-kill-emacs)'

Why bother?

Setting up the daemon has a several advantages.

  1. Files load much faster.
  2. You can share buffers among different emacsclient instances.
  3. If you exit all the emacsclient instances and come back later, your buffers are all still open. When you access a buffer the cursor will be at the exact spot you left off previously.

Downsides

The server does occasionally crash, but that is probably because I am using the bleeding edge version. It hasn’t caused me to lose anything thanks to auto-saving and file recovery so go for it.

References

Running Riak With a Debug Version of the Erlang VM

| Comments

In this post I describe how the easiest way to run Riak with a debug build of the Erlang VM. While I focus specifically on Riak, this procedure should hold true for most Erlang projects that are set up for release builds using rebar.

First, make sure the debug build of Erlang is the first in your path. If you have not built a debug-enabled Erlang VM, I covered how to accomplish that in a prior post and you can find it here.

Next navigate to your Riak repository and do a clean and release build of Riak.

make clean rel

Move to the Riak release directory and open the riak script in an editor.

cd rel/riak
emacs bin/riak

Look for the line EMU=beam. It should be located somewhere around line 228 in the section for the console command. Change that line to EMU=beam.debug and save the file.

Now you are ready to start up Riak. For this example I just start it with console instead of start.

./bin/riak console

You should notice the Erlang banner is slightly different than normal. Using the normal Erlang VM the banner looks something like this:

Erlang R15B (erts-5.9) [source] [64-bit] [smp:4:4] [async-threads:64] [kernel-poll:true]

However using the debug-enabled VM, it should look something like the following:

Erlang R15B (erts-5.9) [source] [64-bit] [smp:4:4] [async-threads:64] [kernel-poll:true] [type-assertions] [debug-compiled] [lock-checking]

And that’s all there is to it. Once you have the debug-enabled Erlang VM built, it’s very easy to get Riak up and running with it. It may go without saying, but Riak will run noticeably slower using the debug-enabled VM so please do not run in this manner in production. Happy debugging!

How to Build a Debug Version of the Erlang VM

| Comments

Last week I needed a debug build of BEAM (the Erlang VM). I decided to write this blog post detailing how to go about building it because I found the process to be cumbersome and the documentation detailing how to do it rather sparse. There is a section in the Erlang documentation that discusses how to build a debug-enabled Erlang runtime, but I found the procedures I will discuss in this post to be more straightforward. Hopefully my experience can help others who may venture down this path, but if nothing else it will serve as a useful reminder for myself the next time I need to do it.

Before starting I should note that all of the following steps have been performed on my MBP running OSX Lion and using Erlang R15B.

To start things off you’ll need to have the Erlang source. Grab the latest version from here if you do not already have it. You may want to move the source tarball into an erlang sub-directory somewhere to keep everything organized.

Next let’s build normally. We will specify the installation destination as a local debug-labeled directory using the --prefix option to the Erlang/OTP configure script, but we will not install until after taking some extra steps to build debug versions of BEAM. One important thing to note is that I have disabled HiPE because I found it to cause build failures when building the debug versions.

The following is a bash script that can be run to accomplish this first step. To run it, save the script to the local directory where you have the erlang source tarball, make it executable, and run it. The script expects an argument that specifies the version of Erlang to build. e.g. If you saved the script as build_erlang.sh, you would run the following: build_erlang.sh R15B.

#! /bin/bash

OTPVERUC:=$(echo $1 || if=- conv=ucase)
OTPVERLC:=$(echo $1 || if=- conv=lcase)
TARBALL=otp_src_$OTPVERUC.tar.gz
LOCAL_DIR=`pwd`

## Build 64-bit OSX
build_64()
{
tar xfz $TARBALL
mv otp_src_$OTPVERUC{,-64}
( cd otp_src_$OTPVERUC-64 \
    && ./configure --enable-debug --enable-threads --enable-kernel-poll \
    --enable-smp-support --enable-darwin-64bit --enable-lock-checking=no --disable-hipe \
    --prefix=/Users/kelly/erlang/$OTPVERLC-debug-64 \
    && make )
}

## Build 64-bit version
build_64

Once the normal build has successfully completed, it’s time to do the debug build. cd to the unpacked erlang source directory (otp_src_R15B-64 if using the script from above). The next step is to export a three environment variables in your shell. The first is ERL_TOP which serves as a reference for many of the build files and scripts. Next is the FLAVOR and the options are either plain or smp. Finally TYPE should be set to debug.


export ERL_TOP=`pwd`
export FLAVOR=smp
export TYPE=debug

Now type make and hit enter and wait. Once the build is complete, there should be a beam.debug.smp file in addition to the normal beam.smp (assuming you set the FLAVOR as smp). To build the other flavor, just change the value of FLAVOR and run make again.

The final step is to install everything. Do this by typing make install and once it completes Erlang/OTP will be installed in R15B-debug-64 including debug-enabled builds of BEAM.

Of course a simplified approach would just be to script all of these steps, so here is the previous build script augmented to also build both flavors of the debug vm and then install everything.

#! /bin/bash

OTPVERUC:=$(echo $1 || if=- conv=ucase)
OTPVERLC:=$(echo $1 || if=- conv=lcase)
TARBALL=otp_src_$OTPVERUC.tar.gz
LOCAL_DIR=`pwd`

## Build 64-bit OSX
build_64()
{
tar xfz $TARBALL
mv otp_src_$OTPVERUC{,-64}
( cd otp_src_$OTPVERUC-64 \
    && ./configure --enable-debug --enable-threads --enable-kernel-poll \
    --enable-smp-support --enable-darwin-64bit --disable-hipe \
    --prefix=$LOCAL_DIR/$OTPVERLC-debug-64 \
    && make \
    && make install \
    && export TYPE=debug; export FLAVOR=plain; make \
    && FLAVOR=smp; make \
    && make install )
}

## Build 64-bit version
build_64

That’s all there is to it. In the next post I will cover how configure Riak to run using the debug-enabled version of Erlang. Until then, happy coding.

A Quickcheck in Time Saves Nine

| Comments

Today I am going to present some anecdotal evidence on the efficacy of property-based testing. The specific tool in this case is Quiviq’s Quickcheck, a property-based testing tool for Erlang. I am not going to spend much time discussing the particulars of property-based testing, but instead focus on a specific real-world instance where it saved me and my employer, Basho, time and money.

The Bare Essentials

Very briefly, here is enough about how property-based testing with Quickcheck works to whet your appetite. When doing property-based testing you specify a set of properties of the code you are testing, create a model, and then test if the model holds for all (or a large number) of permutations of those properties.

My coworker Rusty Klophaus published a very good 3-minute podcast on property-based testing here that you should listen to now if you have not already.

The full version of Quickcheck requires a license which I am fortunate enough to be able to use when testing code for Basho. Contact Quiviq for more information about that or they also have a free version called QuickCheck Mini with a reduced feature set. There are also some open source property testing tools for Erlang such as PropEr, but I do not have any first-hand experience using any of them.

Some Relevant Data

Here are a couple of tidbits that will help you follow the story if you are unfamiliar with how Riak works. * In Riak, the data that is stored is divided among a set of partitions. Understanding the specifics of a partition is not important here, just understand that a partition is a place where data is stored and that to generate a list of all the object keys for a bucket in Riak requires accessing a subset of these partitions. * The number of partitions is configurable.

Story Time

Last week I was working on some changes to how Riak manages key listing. I made the changes I thought were necessary to accomplish my task and did some preliminary manual testing. This involved inserting a set of objects into a bucket and then performing a key listing and verifying that results matched what I expected. This manual testing closely resembles the type of test cases I would likely create for this code using traditional unit tests. Pick a few different sizes of object sets and done. Most developers probably will not spend all day enumerating hundreds of test cases and the result is that the unit test suite only gives you assurance that your code works for a very small number of possible cases. So what about the rest of them? Well, let’s continue the story.

It turned out that there was some intermediate results buffering that I had not accounted for when I made my changes. Each partition would accumulate the object keys it had stored for a bucket, but once this buffer contained 100 keys it would send the keys back to the calling process, empty the buffer, and continue the accumulation. The default number of partitions in Riak is 64. The size of the set of partitions required to list all of the keys from a bucket when there are 64 partitions is 22 (just take my word for it). Assuming a uniform distribution of keys among the partitions, there would need to be at least 2179 objects in a bucket to trigger the intermediate buffering. The largest set of objects I tried in my manual testing was 2000, but fortunately the object set size is one of the inputs that the Quickcheck test for this code generates for me and when I ran the test it quickly found a case where the expected set of object keys did not match the actual set of keys returned.

Now chances are good that a suite of traditional unit tests would have tested an object set greater than 2000, but what if the buffer size that triggered the intermediate buffering was 1000 or 10000. Or what if the number of partitions was set to 128 or 256 instead of 64. The point is that as the number of variables that affect the code under test increases, the number of test case permutations quickly becomes difficult to manage with traditional unit test suites and relies on the developer not to overlook any important cases.

Instead with Quickcheck the developer describes the characteristics of the variables that affect the code and Quickcheck explores the permutations by generating values for the variables and searching for failing cases.

In Summary

Catching and fixing bugs in development saves time and money. Quickcheck helped me find and eliminate this bug early and I have a higher level of confidence that my changes are correct. The case I described here was rather trivial, but it is illustrative of the power and usefulness of property-based testing. Creating Quickcheck tests may take more time than churning out a few unit test cases (especially as you ascend the learning curve), but this is time well spent and the investment will pay dividends.

If you would like to have a look at the Quickcheck test I discussed in this post, you can find it here. Happy testing!

Inertia

| Comments

This is my third or fourth attempt at starting a blog. My past attempts have failed for a variety of reasons. My excuses range from being too busy or distracted to becoming too focused on tinkering with the blog engine to having all my time and energy consumed by a new baby. I am still very busy and there are a lot of other things vying for my attention, but I think this time will be different. My daughter is over eight months old now so I am getting more sleep and with the help of github pages and jekyll I feel like this time I have enough inertia to succeed. I like the convenience of composing posts using markdown and now that I have the setup done and the layout finished, composing new posts should be extremely efficient. I have a backlog of things to write about and there are a lot of exciting things going on at Basho and with my continuing adventures with software that are bound to generate even more ideas. Until next time.