_(Current development is in the [`subx/`](https://github.com/akkartik/mu/blob/master/subx/Readme.md)
sub-directory. That prototype will be promoted to the top-level one day.)_
Mu explores ways to turn arbitrary manual tests into reproducible automated
tests. Hoped-for benefits:
1. Projects release with confidence without requiring manual QA or causing
regressions for their users.
1. Open source projects become easier for outsiders to comprehend, since they
can more confidently try out changes with the knowledge that they'll get
rapid feedback if they break something. Projects also become more
*rewrite-friendly* for insiders: it's easier to leave your project's
historical accidents and other baggage behind if you can be confident of
not causing regressions.
1. It becomes easier to teach programming by emphasizing tests far earlier
than we do today.
The hypothesis is that designing the entire system to be testable from day 1
and from the ground up would radically impact the culture of an eco-system in
a way that no bolted-on tool or service at higher levels can replicate. It
would make it easier to write programs that can be [easily understood by newcomers](http://akkartik.name/about).
It would reassure authors that an app is free from regression if all automated
tests pass. It would make the stack easy to rewrite and simplify by dropping
features, without fear that a subset of targeted apps might break. As a result
people might fork projects more easily, and also exchange code between
disparate forks more easily (copy the tests over, then try copying code over
and making tests pass, rewriting and polishing where necessary). The community
would have in effect a diversified portfolio of forks, a “wavefront” of
possible combinations of features and alternative implementations of features
instead of the single trunk with monotonically growing complexity that we get
today. Application writers who wrote thorough tests for their apps (something
they just can’t do today) would be able to bounce around between forks more
easily without getting locked in to a single one as currently happens.
In this quest, Mu is currently experimenting with the following mechanisms:
1. New, testable interfaces for the operating system. Currently manual tests
are hard to automate because a file you rely on might be deleted, the
network might go down, etc. To make manual tests reproducible it suffices
to improve the 15 or so OS syscalls through which a computer talks to the
outside world. We have to allow programs to transparently write to a fake
screen, read from a fake disk/network, etc. In Mu, printing to screen
explicitly takes a screen object, so it can be called on the real screen,
or on a fake screen inside tests, so that we can then check the expected
state of the screen at the end of a test. Here's a test for a little
text-mode chessboard program in Mu (delimiting the edge of the 'screen'
with dots):
We've built up similarly *dependency-injected* interfaces to the keyboard,
mouse, disk and network.
1. Support for testing side-effects like performance, deadlock-freedom,
race-freeness, memory usage, etc. Mu's *white-box tests* can check not just
the results of a function call, but also the presence or absence of
specific events in the log of its progress. For example, here's a test that
our string-comparison function doesn't scan individual characters unless it
has to:
Another example: if a sort function logs each swap, a performance test can
check that the number of swaps doesn't quadruple when the size of the input
doubles.
Besides expanding the scope of tests, this ability also allows more
radical refactoring without needing to modify tests. All Mu's tests call a
top-level function rather than individual sub-systems directly. As a result
the way the subsystems are invoked can be radically changed (interface
changes, making synchronous functions asynchronous, etc.). As long as the
new versions emit the same implementation-independent events in the logs,
the tests will continue to pass. ([More information.](http://akkartik.name/post/tracing-tests))
1. Organizing code and tests in layers of functionality, so that outsiders can
build simple and successively more complex versions of a project, gradually
enabling more peripheral features. Think of it as a cleaned-up `git log`
for the project. ([More information.](http://akkartik.name/post/wart-layers))
These mechanisms exist in the context of a low-level statement-oriented
language (like Basic, or Assembly). The language is as powerful as C for
low-level pointer operations and manual memory management, but much safer,
paying some run-time overhead to validate pointers. It also provides a number
of features usually associated with higher-level languages: strong
type-safety, function overloading, lexical scope, generic functions,
higher-order functions, and [delimited continuations](http://akkartik.name/coroutines-in-mu).
Mu is currently interpreted and too slow for graphics or sound. Kartik is
working on a way to compile it to native code. Recent activity is all in the
[`subx/`](https://github.com/akkartik/mu/tree/master/subx) directory.
*Taking Mu for a spin*
Mu is currently implemented in C++ and requires a Unix-like environment. It's
been tested on Ubuntu, Mac OS X and OpenBSD; on x86, x86\_64 and ARMv7; and on
recent versions of GCC and Clang. Since it uses no bleeding-edge language
features and has no exotic dependencies, it should work with most reasonable
versions, compilers or processors.
[](https://travis-ci.org/akkartik/mu)
Running Mu will always (re)compile it if necessary:
```shell
$ cd mu
$ ./mu
```
As a simple example, here's a program with some arithmetic:
Mu functions are lists of instructions, one to a line. Each instruction
operates on some *ingredients* and returns some *products*.
```
[products] <- instruction [ingredients]
```
Product and ingredient *reagents* cannot contain instructions or infix
expressions. On the other hand, you can have any number of them. In
partic
# Rudimentary test harness
== code
# instruction effective address register displacement immediate
# . op subop mod rm32 base index scale r32
# . 1-3 bytes 3 bits 2 bits 3 bits 3 bits 3 bits 2 bits 2 bits 0/1/2/4 bytes 0/1/2/4 bytes
#? Entry: # manual test
#? # check-ints-equal(34, 34)
#? # . . push args
#? 68/push "error in check-ints-equal"/imm32
#? 68/push 34/imm32
#? 68/push 34/imm32
#? # . . call
#? e8/call check-ints-equal/disp32
#? # . . discard args
#? 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
#? # syscall_exit(0)
#? bb/copy-to-ebx 0/imm32
#? e8/call syscall_exit/disp32
# print msg to stderr if a != b, otherwise print "."
check-ints-equal: # a: int, b: int, msg: (addr array byte)
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# . save registers
50/push-eax
51/push-ecx
53/push-ebx
# load first 2 args into eax and ebx
8b/copy 1/mod/*+disp8 5/rm32/ebp . . . 0/r32/eax 8/disp8 . # copy *(ebp+8) to eax
8b/copy 1/mod/*+disp8 5/rm32/ebp . . . 3/r32/ebx 0xc/disp8 . # copy *(ebp+12) to ebx
# if (eax == ebx) success
39/compare 3/mod/direct 0/rm32/eax . . . 3/r32/ebx . . # compare eax and ebx
75/jump-if-unequal $check-ints-equal:else/disp8
# . _write(2/stderr, '.')
# . . push args
68/push "."/imm32
68/push 2/imm32/stderr
# . . call
e8/call _write/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
# . return
eb/jump $check-ints-equal:end/disp8
# otherwise print error message
$check-ints-equal:else:
# . _write(2/stderr, msg)
# . . push args
8b/copy 1/mod/*+disp8 5/rm32/ebp . . . 1/r32/ecx 0x10/disp8 . # copy *(ebp+16) to ecx
51/push-ecx
68/push 2/imm32/stderr
# . . call
e8/call _write/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
# . _write(2/stderr, Newline)
# . . push args
68/push Newline/imm32
68/push 2/imm32/stderr
# . . call
e8/call _write/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
# increment Num-test-failures
ff 0/subop/increment 0/mod/indirect 5/rm32/.disp32 . . . Num-test-failures/disp32 # increment *Num-test-failures
$check-ints-equal:end:
# . restore registers
5b/pop-to-ebx
59/pop-to-ecx
58/pop-to-eax
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
== data
# length-prefixed string containing just a single newline
# convenient to have when printing messages and so on
Newline: # (array byte)
# size: int
1/imm32
# data
0a/newline
# every test failure increments this counter
Num-test-failures: # int
0/imm32
# length-prefixed string containing just a single space
Space: # (array byte)
# size: int
1/imm32
# data
20/space
# length-prefixed string containing just a single slash
Slash: # (array byte)
# size: int
1/imm32
# data
2f/slash
# . . vim:nowrap:textwidth=0