Large-scale Software Development with Elisp

Rocky Bernstein:

Talk Overview

Development Requirements — General

Here are the things that are important to me:

  • Of course, I use GNU Emacs.
  • Of course, I use GNU Emacs.
  • I need to be able to break this program into small chunks or modules.

    Implication: there may be many files.

Development Requirements — Organization

  • I need to be able to run and debug each module in isolation.

    Implication: each module needs to have enough information to pull in whatever other modules it needs

Development Requirements — Developing

  • I want quick development turnaround (edit,test,run...)

  • Implications:
    • This means reducing the amount of "compilation" time
    • it means I don't want to have to "install" code to try modules I am interested in.
    • I may have an "installed" version and a "development" version and ...
    • I want to be able to run from the "development" code with little overhead.

Development Requirements — Testing Continued...

  • I need to be able to test each module in isolation.
  • I need to be able to test in batch
  • Test modules are still modules, so see above.

  • Implications:
    • Because there are many files there are many tests
    • Tests need to be able to be run interactively
    • Tests need to be run in batch

The Internal/External Linking Problem

A (large) Emacs Lisp program uses modules from many places. Some modules reside inside and some outside.

Example: location positioning might be such a thing.

C #includes

Interestingly the very old language, C, got it right. Consider:

#include "stdio.h"

which can also be written as:

#include "./stdio.h"


#include <stdio.h>

Ruby requires

In Ruby 1.9 and above:

		require "my_module"

		require_relative "my_module"

load-path is evil

In Emacs Lisp we have load-path which is consulted in load() and require().

Problems with load-path:

  • It is insecure
  • It is complex
  • It is fragile

(length load-path) => 185

Introducing load-relative

I wrote load-relative for internal linking. It is available on ELPA and MELPA.

In its simplest form:

(require 'load-relative) ;; pull in library
(require-relative "my-module")
(require-relative "./my-module") ;; same as above

Introducing load-relative-list

(require-relative-list  '("my-module-a" "my-module-b"))
;; The above is the same as:
(require-relative "my-module-a")
(require-relative "my-module-b")


Schools of Testing:

  • Behavior Driven Development (BDD)
  • Test Driven Development (TDD).

Some Test frameworks.

Emacs Test Unit

I have 66 test files for over 100 Emacs Lisp files. This is more than Emacs 24 using elr.

Let's now dive into a test. This one is from testing test-simple itself:

(load-file "../test-simple.el")
(test-simple-start "test-simple.el")

(note "basic-tests")
(assert-t (memq 'test-simple features) "'test-simple provided")

(assert-nil nil "Knights of ni")
(assert-equal 5 (+ 1 4) "assert-equal")
(assert-raises error (error "you should not see this") "assert-raises")


Emacs Test Run

Inside GNU Emacs

M-x eval-current-buffer

Inside buffer *test-simple* we have:

 0 failures in 4 assertions (0.00102626 seconds)

Inside a POSIX shell terminal:

$ # after ./configure && make
$ make check-short
make -C test check 2>&1  | ruby make-check-filter.rb
0 failures in 4 assertions (0.000122467 seconds)
0 failures in 2 assertions (0.00157458 seconds)
0 failures in 5 assertions (0.000479297 seconds)