Lars Wirzenius Don't burn that bridge!

You may be familiar with some variant of this scenario:

You're on a mailing list (or web forum or Google group or whatever), where some topic you're interested in is being discussed. You see someone saying something you think is wrong. You fire off a quick reply telling them they're wrong, and move on to the next topic.

Later on, you get a reply, and for some reason they are upset at you telling them they're wrong, and you get upset at how rude they are, so you send another quick reply, putting them in their place. Who do they think they are, spouting off falsehoods and being rude about it.

The disagreement spirals and becomes hotter and more vicious each iteration. What common ground there was in the beginning is soon ruined by trenches, bomb craters, and barbed wire. Any bridges between the parties are on fire. There's no hope for peace.

This is called a flame war. It's not a good thing, but it's not uncommon in technical discussions on the Internet. Why does it happen and how can you avoid it?

As someone covered in scars of many a flame war, here are my observations (entirely unsubstantiated by sources):

  • Flame wars happen because people try to be seen as being more correct than others, or to be seen to win a disagreement. This often happens online because the communication medium lacks emotional bandwidth. It is difficult to express subtle emotions and cues over a text-only channel, especially, or any one-way channel.

    Disagreements spiral away more rarely in person, because in-person communication contains a lot of unspoken parts, which signal things like someone being upset, before the thing blows up entirely. In text-only communication, one needs to express such cues more explicitly, and be careful when reading to spot the more subtle cues.

  • In online discussions around free software there are also often no prior personal bonds between participants. Basically, they don't know each other. This makes it harder to understand each other.

  • The hottest flame wars tend to happen in contexts where the participants have the least to lose.

Some advice (again, no sources):

  • Try hard to understand the other parties in a disagreement. The technical term is empathy. You don't need to agree with them, but you need to try to understand why they say what they say and how they feel. As an example, I was once in a meeting where a co-worker arrived badly late, and the boss was quite angry. It was quickly spiralling into a real-life flame war, until someone pointed out that the boss was upset because he needed to get us developers do certain things, and people being late was making that harder to achieve, and at the same time the co-worker who was late was mourning his dog who'd been poorly for years and had recently committed suicide by forcing open a 6th floor window and jumping out.

  • Try even harder to not express anger and other unconstructive feelings, especially by attacking the other parties. Instead of "you're wrong, and you're so stupid that the only reason you don't suffocate is because breathing is an autonomous action that doesn't require the brain, go jump into a frozen lake", say something like "I don't agree with you, and I'm upset about this discussion so I'm going to stop participating, at least for a while". And then don't participate further.

  • Do express your emotions explicitly, if you think that'll mean others will understand you better.

  • Try to find at least something constructive to say, and some common ground. Just because someone is wrong about what the colour of the bike shed should be, doesn't mean you have to disagree whether a bike shed is useful.

  • Realise that shutting up doesn't mean you agree with the other parties in a disagreement, and it doesn't mean you "lose" the argument.

  • Apply rule 6 vigorously: write angry responses if it helps you deal with your emotions, but don't send them. You can then spend the rest of you life being smug about how badly other people have been humiliated and shown to be wrong.

Your homework for this week, should you choose to accept it, is to find an old flame war and read through it and see where the participants could've said something different and defuse the situation. You get bonus points if it's one which you've participated in yourself.

Posted Wed Feb 7 12:00:09 2018 Tags:
Daniel Silverstone Processing input

Computer programs typically need some input on which to perform their purpose. In order to ascribe meaning to the input, programs will perform a process called parsing. Depending on exactly how the author chooses to develop their program, there are a number of fundamentally different ways to convert a byte sequence to something with more semantic information layered on top.

Lexing and Parsing

Lexical analysis is the process by which a program takes a stream of bytes and converts it to a stream of tokens. Tokens have a little more meaning, such as taking the byte sequence "Hello" and representing it as a token of the form STRING whose value is Hello. Once a byte stream has been turned into a token stream, the program can then parse the token stream.

Typically, the parsing process consumes the token stream and produces as its output something like an abstract syntax tree. This AST layers enough semantic meaning onto the input to allow the program to make use of the input properly. As an example, in the right context, a parser might take a token stream of the form STRING(println) '(' STRING(Hello) ')' ';' and turn it into an AST node of the form FunctionInvocation("println", [ "Hello" ]). As you can see, that would be far more useful if the program in question is a compiler.

Parsing in this way is commonly applied when the language grammar in question meets certain rules which allow it to be expressed in such a way that a token stream can be unambiguously converted to the AST with no more than one "look-ahead" token. Such languages can convert "left-to-right" i.e. unidirectionally along the token stream and usually we call those languages LALR(1).

To facilitate easy lexical analysis and the generation of LALR(1) parsers, there exist a number of generator programs such as flex and bison, or re2c and lemon. Indeed such generators are available for non-C languages such as alex and happy for Haskell, or PLY for Python.

Parsing Expression Grammars

PEGs are a type of parser which typically end up represented as a recursive descent parser. PEGs sometimes allow for a parser to be represented in a way which is more natural for the language definer. Further, there is effectively infinite capability for look-ahead when using PEGs, allowing them to parse grammars which a more traditional LALR(1) would be unable to.

Combinatory Parsing

Parser combinators take advantage of higher order functions in programming languages to allow a parser to be built up by combining smaller parsers together into more complex parsers, until a full parser for the input can be built. The lowest level building blocks of such parsers are often called terminal recognisers and they recognise the smallest possible building block of the input (which could be a token from a lexical analyser or could be a byte or unicode character). Most parser combinator libraries offer a number of standard combinators, such as one which will recognise one or more of the passed in parser, returning the recognised elements as a list.

Sadly, due to the strong functional programming nature of combinators, it's often very hard to statically analyse the parser to check for ambiguities or inconsistencies in the grammar. These issues only tend to become obvious at runtime, meaning that if you're using parser combinators to build your parser, it's recommended that you carefully write your grammar first, and convert it to code second.

Homework

Find a program which you use, which consumes input in a form specific to the program itself. (Or find a library which is meant to parse some format) and take a deep look at how it performs lexical analysis and parsing.

Posted Thu Feb 1 12:00:09 2018

There are any number of metrics, heuristics, entrail-reading techniques, etc. which can be used to determine whether or not a project is "alive" for a variety of meanings of that word. Depending on what you need to use a project for, you might need to make such an assessment and so this week I'd like to give you a few ways to assess a software project's "liveness". In addition, you might be a project developer who needs to assess the "liveness" of something you're working on yourself. (For example, to check it's well organised for your expected user-base).

If you're trying to use a project in a production situation, then you likely want to look to whether the codebase is relatively stable, whether it makes sensibly structured releases with some kind of semantic versioning, what its tracker looks like (relatively few open BUGS, short times between bug reports and fixes), and what its documentation looks like. You may also look for evidence that others rely on the project, but that's perhaps less important. It's fine for the codebase to have a lot of churn providing there seems to be some commitment to a stable/reliable interface (be that a CLI or an API).

If, on the other hand, you're looking for a project to use in a fairly experimental situation, you may be looking for a project whose developers are very active and responding well to feature requests, bug reports, etc., however, here the number of open bugs is much less relevant than the way in which the developers react to new uses of their codebase.

If you're looking for a project to contribute to, then assuming you've already determined your own interest in the project to be high, I'd suggest that you look to the developers/community: how active (or otherwise) they are, how well they respond to comments/queries/issues/bugs etc.. A project which is beautifully written but whose developers take months over responding to simple queries might not be suitable for you to join. On the other hand, a nicely written project whose developers are essentially absent is very much ripe for an offer of adoption and/or simple forking.

(As a corollary, if you're a project developer and someone turns up looking at your project and you know it's not right for them, consider pointing them somewhere else.)

So, in summary, useful things to look at include:

  • The churn in the project codebase
  • Whether the project has adopted semantic versioning
  • Whether the project makes lots of bugfix releases or seems stable
  • How well the project team responds to new issues/bugs
  • How well the project accepts contributions.

This week your homework is to assess a project you're part of, with the eyes of someone who might be coming to it to use/contribute-to. See if you can find ways you could improve your own projects, and give it a go.

Posted Wed Jan 24 12:00:07 2018

ssize = recv(fd, NULL, 0, MSG_PEEK|MSG_TRUNC) lets you see how big the next message in a UDP or UNIX socket's buffer is.

This can be important if your application-level communications can support variable message sizes, since you need to be able to provide a buffer large enough for the data, but preferrably not too much larger that it wastes memory.

Unfortunately, a lot of programming languages' bindings don't make this easy.


Python's approach is to allocate a buffer of the provided size, and then reallocate to the returned size afterwards, and return the buffer.

This behaviour is intended to permit a larger allocation to begin with, but as a side-effect it also permits a smaller one to not break horribly.

Well, mostly. In Python 2 it breaks horribly.

$ python -c 'import socket
s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM, 0)
s.bind("/tmp/testsock")
s.recv(0, socket.MSG_PEEK|socket.MSG_TRUNC)'
Traceback (most recent call last):
  File "<string>", line 4, in <module>
SystemError: ../Objects/stringobject.c:3909: bad argument to internal function

Python 3 instead returns an empty buffer immediately before reading the socket.

$ python3 -c 'import socket
s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM, 0)
s.bind("/tmp/testsock")
m = s.recv(0, socket.MSG_PEEK|socket.MSG_TRUNC)
print(len(m), m)'
0 b''

You can work around this by receiving a minimum length of 1.

$ python -c 'import socket
s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM, 0)
s.bind("/tmp/testsock")
m = s.recv(1, socket.MSG_PEEK|socket.MSG_TRUNC)
print(len(m), m)'
(4, 'a\x00n\x00')

The returned buffer's length is that of the message, though most of the buffer's contents is junk.

The reason these interfaces aren't great is that they return an object rather than using a provided one, and it would be unpleasant for it to return a different type based on its flags.

Python has an alternative interface in the form of socket.recv_into, which should fare better, since it can return the size separately, it should be able to translate a None buffer into a NULL pointer.

$ python -c 'import socket
s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM, 0)
s.bind("/tmp/testsock")
m = s.recv_into(None, 0, socket.MSG_PEEK|socket.MSG_TRUNC)
print(m)'
Traceback (most recent call last):
  File "<string>", line 4, in <module>
TypeError: recv_into() argument 1 must be read-write buffer, not None

Unfortunately, this proves not to be the case.

In Python 2 we can re-use a "null byte array" for this purpose.

$ python -c 'import socket
s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM, 0)
s.bind("/tmp/testsock")
nullbytearray = bytearray()
m = s.recv_into(nullbytearray, 0, socket.MSG_PEEK|socket.MSG_TRUNC)
print(m)'
4

Unfortunately, Python 3 decided to be clever.

$ python3 -c 'import socket
s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM, 0)
s.bind("/tmp/testsock")
nullbytearray = bytearray()
m = s.recv_into(nullbytearray, 0, socket.MSG_PEEK|socket.MSG_TRUNC)
print(m)'
0

Like with plain recv it returns early without waiting for the message.

I had hoped to provide a counter-example of a programming language that provided a way to expose this as part of its standard library.

Distressingly, the best behaved standard libraries were the ones that exposed the system calls directly with all the lack of safety guarantees that implies.


In conclusion, MSG_TRUNC is a thing you can do on Linux.

If you want to use it from a higher-level language than C you won't find a good interface for it in the standard library.

If you find yourself in a position to design a language, please bear in mind people may want to do this on Linux, so at least provide access to the un-mangled system call interface.

Posted Wed Jan 17 12:21:03 2018 Tags:

A few months ago we published an article on how to retire. It was followed by my decision to retire Obnam. Today, we will discuss how you know you should retire from a free software project.

Here are some possible reasons:

  • It's no longer fun.
  • You no longer have time for it.
  • It's no longer a useful project.
  • It requires hardware you no longer have.
  • You don't like the technical decisions made in the project and changing them now would be too much work.
  • The project has no users anymore.
  • You have a falling out with the other contributors.
  • You just don't want to work on the project anymore.
  • You win the lottery and can't be bothered to do anything anymore.
  • You become enlightened and realise that computers are preventing you from becoming happy, so you give away everything you have, and move into a cave to meditate.
  • The project is finished and requires no more work: it has all the features its users need, and all bugs have been fixed.

You may also like the talk Consensually doing things together? by Enrico Zini from the 2017 Debconf.

Posted Wed Jan 10 12:00:09 2018

Today I'd like to talk to you about isolation. Not, as some of you might expect given the topics of my more recent ramblings, about personal isolation - but about software isolation.

Software isolation means different things to different people; from something as common as process isolation which happens on almost every computing platform in the modern world, to something less common such virtual machines provided by hypervisors. For today, let's focus on stuff closer to the latter than the former.

Isolating software has, as in all things, both pros and cons. Briefly, some of the reasons you might want to not consider software isolation may include:

  1. It's generally harder work to write software to be isolated easily.
  2. Each isolation point is "yet more" software which either has to be written or at least has to be managed by your operations team.
  3. If you take a complex system, and distribute it among isolated systems, then you have a more complex system than before.

But, since I'm not going to be a Danny Downer today, let's think more about the benefits. Here are three benefits to counter the detractions I listed above.

  1. An isolated software system must have well defined inputs and outputs which can be more effectively documented, mocked, and tested. Leading to:
    1. More easily tested and verified
    2. Easier to debug
    3. Easier to properly document
  2. An isolated software system cannot interfere indirectly with any other software system sharing the same hardware or other infrastructure.
  3. A system comprised of multiple isolated intercommunicating software systems can be more effectively distributed and thus horizontally scaled.

There are any number of different ways to isolate your software systems from one another. These range in "weight" and "completeness of isolation":

  • At the "lightest weight" end of things, we're looking at software written in languages which support virtual environments. This might be Python's venv, Ruby's rvm, or something like luaenv for Lua. These give you only as much isolation as the language's virtual environment support extends to (typically just for the dependencies provided in the language itself).

  • Next we might consider chroots which give you filesystem isolation but very little else. This lets you keep the non-language-specific dependencies isolated too, since effectively a chroot is able to be anything from "just enough to run your app" up to a full install of the "user space" of your operating system.

  • Building from the chroot context, we can reach for a little more isolation in the form of containers. On Linux, these can be managed by such tools as Docker, LXC, systemd-nspawn, or rkt. This approach uses the Linux kernel namespacing facilities to provide further isolation between containers than the above options can do.

  • If containers don't provide enough isolation for you, then you might proceed to hypervisors such as kvm via libvirt, or if you're in a desktop situation then perhaps virtualbox. There're plenty more hypervisors out there though. Hypervisors isolate at the "hardware" level. They effectively provide independently operating virtual hardware elements in the form of emulated hard drives, video cards, etc. Such that the kernel and underlying operating system run just as though they were on real hardware.

  • The final isolation option if not even hypervisors cut it for you, is, of course, simply to have more than one computer and run each piece of your software system on a different machine.

There are any number of software packages out there to help you manage your isolated software environments. From schroot for chroots, through the container providers linked above, and on to hypervisor options. But what's even more exciting, as you start to think about scaling your software horizontally, is that there's also any number of providers out there offering services where they look after the hardware and the management layer, and you get to deploy isolated software elements onto their platform (usually for a fee). These might be hypervisor based virtualisation on Amazon AWS, or any number of providers using Openstack. Where it gets taken to the next level though is that those providers are starting to offer a range of isolation techniques, from containers upward, so whatever your needs, there will be software and providers out there to help you with software isolation.

Your homework… (What? You surely didn't think that just because it's a new year you wouldn't get any homework on the first day?) …is to think about any software systems you're responsible for, and whether they might benefit from having their elements isolated from one another more effectively, so that you might glean some of the benefits of isolation.

Posted Wed Jan 3 12:00:10 2018

When I was young and has just started to learn programming, I read some descriptions of what programming was like as a job. These fell roughly into one of two categories:

  • long meetings, and writing a lot of documentation
  • long days of furious hacking fuelled by chemicals

That was decades ago. I have since learn from experience that a programmer's productivity is best boosted by:

  • knowing clearly what to do
    • a well-defined goal is easier to reach than one that is unclear or changes all the time
  • knowing one's tools and being comfortable with them
    • the exploration phase of using a new tool is rarely very productive
  • not being interrupted while doing it
    • being in the flow is commonly preferred, but may or may not be necessary; however, frequent interruptions are anti-productive in any case
  • getting enough sleep
    • continuous yawning is distracting, concentration is difficult if your brain is hallucinating from lack of sleep
  • avoidance of bad stress
    • stress that makes you worry about the future is bad
  • having a reliable source of lapsang tea
    • but I need to stop drinking it at least six hours before bedtime, or I sleep badly
  • a comfortable work environment

It further helps to use various tools that allow the programmer concentrate on cranking out the code:

  • automated language checkers
    • statically types languages require you to get types right, and will catch you if you mistype a variable name
    • static checkers for dynamically typed languages can often do the same job
    • either way, they help you catch errors earlier, which means you spend less time waiting for an error to happen
  • automated tests
    • unit tests, integration tests, etc, allow you to avoid spending time and effort on doing the same tests manually over and over
  • reuse of high-quality code
    • this means libraries, code generators, tools you can run, etc
    • these save you time by not having to write the code yourself
    • they further save time by not having to debug the code you wrote
    • however, beware of low-quality code; it won't save you time, since you'll almost certainly have to debug and fix it yourself
  • automated systems to run your automated tests
    • continuous integration runs without you having to remember to run your tests
    • it also runs them in the background, meaning you don't need to wait for them to finish, which is especially useful when you have slow tests
    • the tests also get run in a well-defined environment, rather than the developer's randomly configured ever-changing laptop
  • automated system for installing software on target machines
    • continuous delivery takes care of installing your software somewhere you can use it, which may or may not be the production server
    • this is especially useful for server, web, and command line software, but is generally useful
    • often the process of installing manually is tedious - just like doing manual testing - so the more it's automated the less time you waste on repetitive work
Posted Thu Dec 28 12:00:08 2017 Tags:

We've previously spoken about how your workstation area matters, mentioning topics such as consistency in input surfaces, arrangement of screens, good chairs, etc. Today I'd like to extend that to talk about the general environment in which you're working/hacking/playing.

As with all these little articles, I have a story to tell, and this one is about how my heating boiler died at home and I had to spend two weeks with a computer room which was consistently below 10 degrees celsius unless I spent a lot of electricity heating it up. Sitting in the cold at my keyboard was difficult. You can put jumpers on, and generally keep yourself warm, but for at least some of us, our hands still get cold, and that can be quite unpleasant when you're a keyboard worker.

The temperature of your working/hacking/playing environment will affect your effectiveness dramatically. If it's too cold, or too warm, you will not be able to think properly and your reaction times will grow. Keeping the area around your workstation clear of clutter will also improve your ability to think, though some do benefit from having one or two fiddling things for when you need to do something with your hands while your brain wanders off to solve the problem.

I recommend ensuring that there's not too much in the way of noise distraction at your workstation too. Music is something that some of us find helps, as can a white noise generator or rain generator. I sometimes use an internet cat to help me relax, but it's important to try and block out sounds which you cannot predict or control such as building site noise, other people talking, etc.

For me, it's also important to try and eliminate unexpected or unpleasant scents from your workstation too. I find that I cannot concentrate if I can smell food (sometimes a big issue because my neighbours love to fry onions at times of day when I'm hungry).

Keep hydrated. While that's not entirely an environmental thing, ensuring that you have an adequate supply of water, squash, tea, or whatever you prefer, is very important. Bad hydration causes your brain to behave similarly to being drunk or super-tired. Obviously if you're going to keep hydrated, don't try and sit and work through a full bladder, that can be just as distracting/unhelpful.

Your homework for this week is to look critically at your work/hack/play environment and make at least one small change for the better. Perhaps you could comment below about a change you made and how you feel it has affected you, either positively or negatively. If you have other tips for improving your work/hack/play space then also include them below.

Posted Wed Dec 13 12:00:10 2017
Richard Ipsum Property Testing in C

One nice thing about Haskell is Quickcheck, if you're not familiar with Haskell or Quickcheck it works like this:

Prelude> let evens xs = [ x | x <- xs, x `mod` 2 == 0 ]
Prelude> evens [1..10]
[2,4,6,8,10]

Here we define a function that takes a list of numbers and returns only the even numbers. We can use Quickcheck to test this function:

Prelude> import Test.QuickCheck
Prelude Test.QuickCheck> let test_evens xs = [ x | x <- evens xs, x `mod` 2 /= 0 ] == []
Prelude Test.QuickCheck> quickCheck test_evens
+++ OK, passed 100 tests.

Here we define a test function that asserts that a list of even numbers shouldn't contain any odd ones. Passing this function to Quickcheck shows that the function passed 100 tests. Quickcheck sees that our test_evens function takes a list of numbers and returns a boolean, and it tests our code by generating a set of random inputs and executing it with those inputs.

Clearly this type of testing can be very useful, and not just for Haskell programs. Theft is a C library that brings Quickcheck style testing to C. This sort of testing is known as Property Testing.

Let's reimplement our Haskell code in C,

#include <stdio.h>
#include <stdlib.h>
#include <theft.h>

struct IntArray {
    int len;
    int arr[];
};

struct IntArray *evens(struct IntArray *input)
{
    int nevens = 0;
    struct IntArray *output;

    if (input == NULL) {
        return NULL;
    }

    output = malloc(sizeof (struct IntArray) + input->len * sizeof(int));

    if (output == NULL) {
        return NULL;
    }

    for (int i = 0; i < input->len; i++) {
        if (input->arr[i] % 2 == 0) {
            output->arr[nevens++] = input->arr[i];
        }
    }

    output->len = nevens;
    return output;
}

Here we define a function that takes an array of integers and outputs an array of even integers.

Now let's do some testing!

Since C is not strongly typed like Haskell we need to define a function that describes what a test input should look like. Providing this information to Theft will allow it to generate real test input data. The function should have the following prototype,

enum theft_alloc_res allocate_int_array(struct theft *, void *, void **)

Let's write it!

enum theft_alloc_res allocate_int_array(struct theft *t, void *data, void **result)
{
    int SIZE_LIMIT = 100;

    int size = theft_random_choice(t, SIZE_LIMIT);

    struct IntArray *numbers = malloc(sizeof (struct IntArray) + size * sizeof(int));

    if (numbers == NULL) {
        return THEFT_ALLOC_ERROR;
    }

    for (int i = 0; i < size; i++) {
        numbers->arr[i] = theft_random_choice(t, INT_MAX);
    }

    numbers->len = size;

    *result = numbers;

    return THEFT_ALLOC_OK;
}

theft_random_choice is a function that will pick a random number between 0 and some defined limit. The result is not truly random, but instead based on the complexity of the input Theft requires. The documentation for Theft points out that the main thing with this is to ensure that wherever theft_random_choice returns 0 our alloc_int_array function should return the simplest input possible, in our case that would be an empty array.

Theft passes a reference pointer to the alloc_int_array function, this must be updated to point to the array we have allocated before the function returns with THEFT_ALLOC_OK. In the event of some kind of error the function should return THEFT_ALLOC_ERROR

Next we write the property function, this function takes an input array of integers generated by Theft, runs our evens function over that input and asserts that the resultant output doesn't contain any odd numbers.

enum theft_trial_res property_array_of_evens_has_no_odd_numbers(struct theft *t, void *test_input)
{
    struct IntArray *test_array = test_input;

    struct IntArray *result = evens(test_array);

    // Array of even numbers should not contain any odd numbers
    for (int i = 0; i < result->len; i++) {
        if (result->arr[i] % 2 != 0) {
            return THEFT_TRIAL_FAIL;
        }
    }

    return THEFT_TRIAL_PASS;
}

Putting this together, we define some boiler plate to cover the various functions we just defined for generating test inputs,

struct theft_type_info random_array_info = {
    .alloc = allocate_int_array,
    .free = theft_generic_free_cb,
    .autoshrink_config = {
        .enable = true,
    }
};

The alloc member is updated to point to the function we just defined. Since the test inputs are dynamically allocated with malloc they will need to be freed later on. Theft provides a generic function for freeing which is sufficient for our purposes: theft_generic_free_cb.

The last member of this structure needs more explanation. If Theft encounters an input which causes the test to fail, it will try to pare down the input to the smallest input that causes failure; this is called shrinking.

Theft lets you define a function that can provide some control over the shrinking process, or it can use its own shrinking functions: autoshrinking. If autoshrinking is used however, the function that allocates test inputs must base the complexity of the input it generates upon the result of one of the theft_random functions, such as theft_random_bits, or theft_random_choice. This is why our alloc_int_array function uses theft_random_choice rather than standard pseudo random number generating functions.

Finally we write a function to execute the tests,

int main(void)
{
    theft_seed seed = theft_seed_of_time();
    struct theft_run_config config = {
        .name = __func__,
        .prop1 = property_array_of_evens_has_no_odd_numbers,
        .type_info = { &random_array_info },
        .seed = seed
    };

    return (theft_run(&config) == THEFT_RUN_PASS) ? EXIT_SUCCESS : EXIT_FAILURE;
}

Compiling and running:

$ gcc -o test test.c -ltheft
$ ./test 

== PROP 'main': 100 trials, seed 0x62a401b7fa52ac8b
.......................................................................
.............................
== PASS 'main': pass 100, fail 0, skip 0, dup 0

I hope this helps anyone looking to try out Property Testing in C. Another guide that might be useful can be found here, it has an example that uses a manually defined shrinking function, which may be useful for more complex situations.

Posted Wed Dec 6 12:00:07 2017
Daniel Silverstone Advent Of Code, 2017

Previously I mentioned the Advent of Code as a possible thing you might want to look at for using as a way to learn a new language in a fun and exciting way during December.

This year, it'll be running again, and I intend to have a go at it again in Rust because I feel like I ought to continue my journey into that language.

It's important to note, though, that I find it takes me between 30 and 90 minutes per day to engage properly with the problems, and frankly the 30 minute days are far more rare than the 90 minute ones. As such, I urge you to not worry if you cannot allocate the time to take part every day. Ditto if you start and then find you cannot continue, do not feel ashamed. Very few Yakking readers are as lucky as I am in having enough time to myself to take part.

However, if you can give up the time, and you do fancy it, then join in and if you want you can join my private leaderboard and not worry so much about competing with the super-fast super-clever people out there who are awake at midnight Eastern time (when the problems are published). If you want to join the leaderboard (which contains some Debian people, some Codethink people, and hopefully by now, some Yakking people) then you will need (after joining the AoC site) to go to the private leaderboard section and enter the code: 69076-d4b54074. If you're really enthusiastic, and lucky enough to be able to afford it, then support AoC via their AoC++ page with a few dollars too.

Regardless of whether you join in with AoC or not, please remember to always take as much pleasure as you can in your coding opportunities, however they may present themselves.

Posted Wed Nov 29 12:00:07 2017