www.BrettDaniel.com

What to Do When Chemotherapy Stops Working

Chest PET scan animation showing tumors in the lungs.

The animation above shows my most recent PET scan. See the bright yellow sparkles in the pair of otherwise dark areas above the center of the image? Those are the tumors in my lungs. Some appeared since the last set of scans; others are metabolizing more, and thus showing up brighter. For comparison, the left image below is from last November, and the right is from this most recent scan.

PET scan showing my lungs after two rounds of chemotherapy.  The small tumors have decreased in brightness. Chest PET scan

These are not good results. Obviously, the chemotherapy has not prevented tumor growth. Medical literature would say I have reached the regimen's "time to progression", that is, the point after starting treatments at which the disease advances or reappears. Despite the provocative title of this post, that does not necessarily mean that the chemotherapy has stopped working; it just means it is time to try something else.

Unfortunately, my options have become much more limited over the past year. By last March, I had long passed the maximum lifetime dosage of one drug (ifosfamide). Last July, side effects prompted my oncologists to remove another (docetaxel) from my treatments. These latest results show that the remaining drug (gemcitabine) alone has not been effective.

My oncologist searched through the medical literature for other treatments. There is not much, since I have already tried almost everything, and the pool of patients with recurrent, metastatic osteosarcoma is so small as to limit the number of clinical trials for new drugs. Nevertheless, he found one clinical case study in which a combination of gemcitabine and a drug called irinotecan most often used to treat colon cancer was beneficial in a case very similar to mine.

The combination of gemcitabine and irinotecan has shown acceptable toxicity and synergistic activity against many refractory solid tumors. Our case report demonstrates an excellent clinical and radiographic response in a heavily pretreated patient with recurrent osteogenic sarcoma...Due to the absence of a radiographic response to gemcitabine and irinotecan used as single agents, we think that synergism between the 2 drugs makes their combination effective.

So far I have undergone one cycle of treatments with this new pair of drugs. Time will tell if it will work for me.

But truthfully, while I remain optimistic, I don't expect to find the silver bullet. At best, this new regimen will most likely just stabilize tumor growth, and the prospects of experimental treatments or more drastic alternatives like surgery or radiation are not much better. That makes, "try something else and see if it works," an unsatisfying answer to the question posed in the title of this post, but it is the only answer I have for now.

Beware Mutable Data Points (Updated)

In my previous post, I mentioned that one of the benefits of JUnit Theories is that they decouple test inputs (data points) from test implementation (Theories). However, this benefit comes at a price: since data points may be reused across several Theories, the way in which one defines mutable data points can cause surprising unexpected behavior.

To illustrate the problems that can occur with mutable data points, I will reuse the Counter example from the previous post. Counters are obviously mutable because calling increment increases a counter's value by one.

Say I have two Theories: incrementTheory, which is described in the previous post, and equalIncrementTheory, which verifies that two (un)equal counters remain (un)equal after incrementing both. Both theories mutate the counters passed in as arguments.

@Theory
public void incrementTheory(Counter toIncrement) {
    System.out.println("incrementTheory(" + toIncrement + ")");
    int oldValue = toIncrement.getValue();
    toIncrement.increment();
    int newValue = toIncrement.getValue();
    assertEquals(oldValue + 1, newValue);
}

@Theory
public void equalIncrementTheory(Counter c1, Counter c2) {
    System.out.println("equalIncrementTheory(" + c1 + ", " + c2 + ")");
    boolean wereEqual = c1.equals(c2);
    c1.increment();		
    c2.increment();
    assertEquals(wereEqual, c1.equals(c2));
}

There are four ways in which I can define data points that JUnit will pass to these Theories. In the previous post, I used the @DataPoints annotation to mark a method that returns an array of Counter objects. Each element of the array is a single data point. I could have alternately used the @DataPoint (no "s") annotation on a method that returns a single counter, or used either annotation to mark static fields in the same manner.

The following list shows each of these four alternatives, followed by the output from the print statements included in the theories shown above. In each case there are two data points of type Counter initialized to the values 0 and 5.

  1. @DataPoint on a field holding a single value
    @DataPoint 
    public static Counter ZERO = new Counter(0);
    @DataPoint 
    public static Counter FIVE = new Counter(5);
    
    outputs
    incrementTheory(Counter(0))
    incrementTheory(Counter(5))
    equalIncrementTheory(Counter(1), Counter(1))
    equalIncrementTheory(Counter(3), Counter(6))
    equalIncrementTheory(Counter(7), Counter(4))
    equalIncrementTheory(Counter(8), Counter(8))
    
  2. @DataPoints on a field holding an array
    @DataPoints
    public static Counter[] COUNTERS = {
        new Counter(0),
        new Counter(5),
    }; 
    
    outputs
    incrementTheory(Counter(0))
    incrementTheory(Counter(5))
    equalIncrementTheory(Counter(1), Counter(1))
    equalIncrementTheory(Counter(3), Counter(6))
    equalIncrementTheory(Counter(7), Counter(4))
    equalIncrementTheory(Counter(8), Counter(8))
    
  3. @DataPoint on a method that returns a single value
    @DataPoint
    public static Counter zero() {
        return new Counter(0);
    }
    @DataPoint
    public static Counter five() {
        return new Counter(5);
    }
    
    outputs
    incrementTheory(Counter(5))
    incrementTheory(Counter(0))
    equalIncrementTheory(Counter(5), Counter(5))
    equalIncrementTheory(Counter(5), Counter(0))
    equalIncrementTheory(Counter(0), Counter(5))
    equalIncrementTheory(Counter(0), Counter(0))
    
  4. @DataPoints on a method that returns an array
    @DataPoints
    public static Counter[] counters() {
        return new Counter[] {
            new Counter(0),
            new Counter(5),
        };
    }
    
    outputs
    incrementTheory(Counter(0))
    incrementTheory(Counter(5))
    equalIncrementTheory(Counter(0), Counter(0))
    equalIncrementTheory(Counter(1), Counter(5))
    equalIncrementTheory(Counter(5), Counter(0))
    equalIncrementTheory(Counter(6), Counter(5))
    

Three things are surprising about this output. First, even though the alternatives start with the same data points, their output differs. Second, the data points started with values 0 and 5, but the values 1, 3, 4, 6, 7, and 8 all appear in various places. Indeed, in alternatives 1 and 2, the values 0 and 5 are never found in equalIncrementTheory! Third, alternative 3 is the only one that gives the "expected" output in which both theories are executed with all combinations of 0 and 5.

All of these issues are caused by mutable data points. One Theory execution (meaning a single call to incrementTheory or equalIncrementTheory) can affect later Theory executions.

To understand why this happens, it is necessary to examine the "lifespan" of each data point instance. Fields such as those in alternatives 1 and 2 are declared static and initialized when the class is loaded. Therefore, the data point instances held by the fields live across all theory executions. For alternative 3, JUnit calls the zero and five methods once before each theory execution, so each data point instance lives through only one method call. Alternative 4 produces arrays of data points that live across multiple executions of a single theory but are reinitialized after each element has been used in a theory at least once.

To make these distinctions more clear, the following pseudocode illustrates the entire test run for each alternative. It shows the points at which JUnit creates each data point instance (represented as variable definition), the datapoint values (shown in comments), how they are passed to Theories (represented as method calls), and where they are mutated (the increment calls).

  1. @DataPoint on a field holding a single value. Note that ZERO and FIVE live for the entire test run and are continually mutated.
    ZERO = new Counter(0)
    FIVE = new Counter(5)
    incrementTheory(ZERO) // 0 
        ZERO.increment() // 1
    incrementTheory(FIVE) // 5
        FIVE.increment() // 6
    equalIncrementTheory(ZERO, ZERO) // 1, 1
        ZERO.increment() // 2
        ZERO.increment() // 3
    equalIncrementTheory(ZERO, FIVE) // 3, 6
        ZERO.increment() // 4
        FIVE.increment() // 7
    equalIncrementTheory(FIVE, ZERO) // 7, 4
        FIVE.increment() // 8
        ZERO.increment() // 5
    equalIncrementTheory(FIVE, FIVE) // 8, 8
        FIVE.increment() // 9
        FIVE.increment() // 10
    
  2. @DataPoints on a field holding an array. Like the previous alternative, the COUNTERS array lives for the entire test run and its elements are continually mutated.
    COUNTERS = new Counter[] { new Counter(0), new Counter(5) }
    incrementTheory(COUNTERS[0]) // 0
        COUNTERS[0].increment() // 1
    incrementTheory(COUNTERS[1]) // 5
        COUNTERS[1].increment() // 6
    equalIncrementTheory(COUNTERS[0], COUNTERS[0]) // 1, 1
        COUNTERS[0].increment() // 2
        COUNTERS[0].increment() // 3
    equalIncrementTheory(COUNTERS[0], COUNTERS[1]) // 3, 6
        COUNTERS[0].increment() // 4
        COUNTERS[1].increment() // 7
    equalIncrementTheory(COUNTERS[1], COUNTERS[0]) // 7, 4
        COUNTERS[1].increment() // 8
        COUNTERS[0].increment() // 5
    equalIncrementTheory(COUNTERS[1], COUNTERS[1]) // 8, 8
        COUNTERS[1].increment() // 9
        COUNTERS[1].increment() // 10
    
  3. @DataPoint on a method that returns a single value. JUnit calls the zero and five methods once for each Theory argument. This is analogous to continually assigning temporary variables.
    tmp1 = five()
    incrementTheory(tmp1) // 5
        tmp1.increment() // 6
    tmp2 = zero()
    incrementTheory(tmp2) // 0
        tmp2.increment() // 1
    tmp3 = five()
    tmp4 = five()
    equalIncrementTheory(tmp3, tmp4) // 5, 5
        tmp3.increment() // 6
        tmp4.increment() // 6
    tmp5 = five()
    tmp6 = zero()
    equalIncrementTheory(tmp5, tmp6) // 5, 0
        tmp5.increment() // 6
        tmp6.increment() // 1
    tmp7 = zero()
    tmp8 = five()
    equalIncrementTheory(tmp7, tmp8) // 0, 5
        tmp7.increment() // 1
        tmp8.increment() // 6
    tmp9 = zero()
    tmp10 = zero()
    equalIncrementTheory(tmp9, tmp10) // 0, 0
        tmp9.increment() // 1
        tmp10.increment() // 1
    
  4. @DataPoints on a method that returns an array. JUnit calls the method once for each Theory argument, then loops through all combinations of the returned arrays' values. In the case of equalIncrementTheory, it reuses the left argument across two Theory executions while the right argument changes.
    tmp1 = counters() // { 0, 5 }
    incrementTheory(tmp1[0]) // 0
        tmp1[0].increment() // 1
    incrementTheory(tmp1[1]) // 5
        tmp1[1].increment() // 6
    tmp2 = counters() // { 0, 5 }
    tmp3 = counters() // { 0, 5 }
    equalIncrementTheory(tmp2[0], tmp3[0]) // 0, 0
        tmp2[0].increment() // 1
        tmp3[0].increment() // 1
    equalIncrementTheory(tmp2[0], tmp3[1]) // 1, 5
        tmp2[0].increment() // 2
        tmp3[1].increment() // 6
    tmp4 = counters() // { 0, 5 }
    equalIncrementTheory(tmp2[1], tmp4[0]) // 5, 0
        tmp2[1].increment() // 6
        tmp4[0].increment() // 1
    equalIncrementTheory(tmp2[1], tmp4[1]) // 6, 5
        tmp2[1].increment() // 7
        tmp4[1].increment() // 6
    

This behavior certainly violates The Principle of Least Astonishment, but is it necessarily undesirable? Theories should test behavior common to many data points, including those that may or may not have been mutated elsewhere, so perhaps it is a good thing that alternatives 1, 2, and 4 "create" data points that the user did not originally plan for. However, common practice dictates that unit tests should be independent of each other and deterministically repeatable. Alternatives 1, 2, and 4 violate both principles: one Theory execution can affect another, and since Theory ordering is nondeterministic, there is no guarantee that re-running a Theory will yield the same result.

Therefore, when writing Theories that operate on mutable data points, I most often use and recommend the third alternative. That way each Theory execution uses new data point instances, making Theories independent of each other and deterministic.

Update (September 29, 2009)

A reader emailed with the observation that the issues surrounding mutable versus immutable objects are not specific to JUnit. Immutable objects make a program easier to reason about since changing a value in one place cannot affect another place that reads the value. The same is true in JUnit: using immutable objects causes all four ways of defining data points to produce equivalent behavior. However, using immutable objects may not be feasible or preferable, so one must be aware of how mutable objects are initialized and used. As the above article shows, this task is more difficult with JUnit Theories since it is not always obvious when data points are (re)initialized and how they flow across multiple Theories.

JUnit Theories

This semester I am overseeing two undergraduate senior theses. The two students are working on a project involving JUnit Theories. Theories are a very useful feature of JUnit, but they have not been widely adopted since they are still experimental and not documented very extensively. The project's short-term goal is to address this problem by writing a suite of Theories for use as benchmarks in testing research. In the longer term, we hope to apply knowledge gained by writing Theories to other research projects and find areas in which Theories can be improved.

This post describes what Theories are and what they do. In future posts, I hope to write about why I find them interesting and how they enable more complex testing tasks.

"Theories in practice: Easy-to-write specifications that catch bugs" defines Theories in the following way1:

[Theories] are partial specifications of program behavior. Theories are written much like like test methods, but are universally quantified: all of the theory’s assertions must hold for all arguments that satisfy the assumptions...A theory can be viewed in several ways. It is a universally quantified ("for-all") assertion, as contrasted to an assertion about a specific datum. It is a generalization of a set of example-based tests. It is a (possibly partial) specification of the method under test.

To understand what this definition means, it is easiest to explain a simple example. Say we have a simple Counter class that allows one to increment an integer value every time the increment method is called.

public class Counter {
    private int value;

    public Counter(int init) {
        this.value = init;
    }

    public void increment() {
        value = value + 1;
    }
		
    public int getValue() {
        return value;
    }
}

We wish to test that incrementing always increases a counter's value by one. The standard way to test this functionality is to write an example-based unit test that creates a Counter, increments it a few times, and asserts that the incremented values are correct.

@Test 
public void testIncrement()	{
    Counter c = new Counter(3);
    c.increment();
    assertEquals(4, c.getValue());
    c.increment();
    assertEquals(5, c.getValue());
}

This is a useful test, but it only verifies that a single counter initialized to three is incremented correctly. It would be good to test additional counters initialized to many different values. Doing so using example-based testing requires multiple test methods or testing objects in a loop.

Theories provide an elegant alternative that complements example-based tests. From the test writer's point of view, Theories are just like normal unit tests but with one or more parameters.2 To test incremement, one can write a Theory that accepts a Counter object, increments it, then asserts that the value has increased by one. This is more general than an example-based test because it verifies a property common to all counters, regardless of how they were initialized. In the following code, the incrementTheory method implements such a Theory.

@RunWith(Theories.class)
public class CounterTheories {
   
    @DataPoints 
    public static Counter[] data() {
        return new Counter[] {
            new Counter(0),
            new Counter(1),
            new Counter(-1),
            new Counter(Integer.MIN_VALUE),
            new Counter(Integer.MAX_VALUE), // overflows when incremented
        };
    }

    @Theory
    public void incrementTheory(Counter toIncrement) {
        int oldValue = toIncrement.getValue();
        assumeTrue(Integer.MAX_VALUE != oldValue);
        toIncrement.increment();
        int newValue = toIncrement.getValue();
        assertEquals(oldValue + 1, newValue);
    }

    //... more theories 
}

The @RunWith(Theories.class) annotation tells JUnit that it should run all methods in the class that are annotated with @Theory. The @DataPoints annotation marks methods that return values that JUnit should supply to applicable Theories. At runtime, JUnit matches the values returned from data point methods or fields to appropriate Theory parameters.3 In the example above, it sees that Counter objects are produced by data and consumed by incrementTheory, so it executes incrementTheory once for each element in the array returned from data.

Theories decouple test inputs from test implementation. Data points are automatically reused across multiple theories (even in subclasses), making it easier to write new tests. Adding a data point often provides a value that a test writer may not have originally considered, thus improving all Theories that use the data point.

But certain data points may not be applicable to a particular Theory. The test writer describes what data points apply by using methods provided by the org.junit.Assume class. Assumptions are similar to normal assertions except they cause a Theory to skip certain data points rather than fail. In our example, incrementTheory should not increment a counter whose value is equal to Integer.MAX_VALUE since the value would overflow. Therefore, incrementTheory uses assumeTrue to check for this special case.

This summary and example briefly describes the basics of Theories but glosses over how Theories work internally and lacks practical advice like how to write "good" theories. I hope that the undergrads and future weblog posts can explore these topics and deeper research issues further.

Notes

  1. See also an early paper on Theories and the initial announcement of their inclusion as part of an experimental project called Popper.
  2. .NET offers a similar feature but calls it—perhaps more descriptively—parameterized unit tests. JUnit uses this term to mean test classes that are instantiated with input data.
  3. Internally, JUnit finds data points whose declared types derive from the declared types of Theory parameters. It does not currently box and unbox primitive data points. I submitted a simple patch that fixes the problem, but it has not yet been accepted.

ReAssert at ASE 2009

In my previous post, I wrote about ReAssert, the tool I built to automatically fix broken unit tests. Yesterday I received notification that the paper describing the tool got accepted to ASE 2009.

This is the same paper mentioned in my crunch time analysis and typography request.

Here is the (working) abstract:

Developers often change software in ways that cause tests to fail. When this occurs, developers must determine whether failures are caused by errors in the code under test or in the test code itself. In the latter case, developers must repair failing tests or remove them from the test suite. Fixing tests is time consuming but beneficial, since removing tests reduces a test suite's ability to detect regressions. Fortunately, simple program transformations can repair many failing tests automatically.

We present ReAssert, a novel technique and tool that suggests repairs to failing tests' code which cause the tests to pass. Examples include replacing literal values in tests, changing assertion methods, or replacing one assertion with several. If the developer chooses to apply the repairs, ReAssert modifies the code automatically. Our experiments show that ReAssert can repair many common test failures and that its suggested repairs correspond to developers' expectations.

The conference will be held in Auckland, New Zealand. I am excited to travel overseas to present my work. Vilas, my coauthor, and Yun-Young, my officemate, are both from New Zealand and are eager to visit home.

Update September 8, 2009

I have posted the final version of the paper and updated the ReAssert homepage.

Update November 30, 2009

The conference presentation went very well, and I got a great deal of insightful questions and feedback from other attendees. Here are the presentation slides. I told the story of Alice the software developer like in the previous ReAssert post. The presentation starts with a picture of Alice adapted (as per Creative Commons) from xkcd #662.

Alice the software developer. Adapted from xkcd #662 and used in my ReAssert presentation.

I had originally planned to draw a picture in my normal cartoon style, but decided instead to use something simpler. The xkcd picture turned out to be a good choice; it made the audience laugh, and one attendee mentioned Scott McCloud's assertion from Understanding Comics that a simple face causes the audience to identify themselves in a character.

ReAssert: Suggesting Repairs for Broken Unit Tests

For the past year or so, I have been researching how software tests fail and the ways in which developers fix the failures. There are many interesting problems within this general theme, but I have most recently focused on the following familiar scenario:

Alice is a developer a large software company. She works on the company's flagship product and spends over half of her time writing unit tests to verify her code and document her assumptions. She is not alone in this respect; the company requires that functional changes and bugfixes should have corresponding unit tests to prevent regressions. As a result, the product's unit test suite achieves exceptionally high coverage.

One day, the project manager informs Alice that a key requirement has changed. The changed requirement violates many assumptions encoded the test suite, so several dozen tests fail after Alice modifies the software. Now Alice has a choice: should she remove the failing tests since they no longer reflect the correct behavior of the software, or should she attempt to repair the tests, which would require tedious and time-consuming manual editing?

Developers often have to make a similar choice. When tests fail due to problems with test code rather than the system under test, it is undoubtedly beneficial to fix the broken tests, since removing tests reduces a test suite's ability to detect regressions. However, developers may not take the time to fix the broken tests. For example, while working on the refactoring paper, my colleagues and I found many of Eclipse's refactoring tests were either commented out, marked as ignored, or most bizarrely, bypassed using "if (true) return;".

To solve this problem, I have been exploring ways of reducing the effort required to fix broken unit tests. Doing so would make developers less fearful of "deep" changes, allow them to write more detailed tests, and most importantly, provide time for more important work.

As a first step toward this goal, I developed a tool called ReAssert that automatically suggests changes to test code that are sufficient to make tests pass. Earlier this week I released a public beta. I welcome anyone reading this to download it from the ReAssert project homepage and try it out. Please contact me if you have any comments, questions, ideas for improvement, or bug reports.

Side Effects

When discussing most medications, doctors usually list expected benefits first, then possible side effects. For chemotherapy drugs, the discussion is reversed: first doctors list expected side effects, then possible benefits. Each drug has its own unique side effect "signature" and regimen of secondary drugs with their own set of side effects. I have already written about hair loss and strange dreams. I recently experienced a new unexpected side effect: something caused my right leg to swell like an overstuffed sausage.

It is a frustratingly inexplicable side effect. It occurs after the Docetaxel treatments I have gotten every third week for the last few months. The drug's documentation says it occurs "in rare cases" but the root cause is not well known. I got a series of tests to rule out particularly bad possibilities like poor circulation, a blood clot, or problems with my lymphatic system. My surgeon says it does not appear to be related to surgery. That makes it just another random, mildly annoying side effect of chemotherapy. It didn't hurt; I just had to tie my shoe more loosely.

But what to do about it? The doctor prescribed some medication to reduce the swelling. It seems to be working. In the six days between treatments, I lost nine pounds, all from the leg! Of course, the medicine has its own set of side effects. Fatigue, in particular, but that goes away when I take another pill (for unrelated reasons) whose side effects include insomnia.

To test whether the swelling is really caused by the Docetaxel, the doctor omitted it from this week's treatment. She is still deciding whether to reduce the dosage, spread it out, or leave it unchanged. In any case, I am more than happy to accept a sausage leg if the drug takes care of the cancer.

Just got back from canoeing at…

Just got back from canoeing at Kickapoo SP. Beautiful and peaceful float. Barely had to paddle.

Silly Videos

A friend complained that I had not posted any new videos since creating a YouTube account for the JUnit Music Box demonstration. So here are some silly videos from several years ago.

The 2006 Winter Olympics curling event provided a convenient target for Nerf suction cup darts.

My grandmother's and cousin's dogs were just puppies in August 2004.

In October 2004, Eric demonstrated his unicycle skills in the hallway of Purdue's Hillenbrand Hall.

In February 2004, my suitemate Matt hung a rope swing from the loft in his dorm.

At my sister's high school graduation party in June 2003, the pinata did not work quite as expected.

Last night’s thunderstorm knoc…

Last night's thunderstorm knocked out power in half (?) of my apartment and fried my router. Somehow all of my other electronics are fine.

Lego Siebel Center

I recently read that Lego released models of The Guggenheim and Fallingwater as a part of its new "Architecture" series. That news inspired me to design a Lego version of the University of Illinois' Thomas M. Siebel Center for Computer Science.

Lego Siebel Center facing southwest

I cannot comment on how Siebel Center's architecture compares to The Guggenheim or Fallingwater, but the building has some interesting features that were a challenge to translate into Lego. In particular, choosing the correct scale, building the angled sections, and sculpting the topology of the courtyard took a lot of experimentation. Fortunately, Siebel Center is one of the most photogenic buildings on campus, so the web is filled with pictures that I could reference [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].

The key to figuring out an appropriate scale came from this detailed floor plan. I needed a scale that provided a good level of detail, allowed walls and other structures to be subdivided into "nice" Lego sizes, and produced a model of reasonable size. As is often the case, the simplest solution was the best: if I used a single 1×1×1 block for each window, then everything fell together like magic. At this scale, the full model is about 70 studs (≈22 inches) long by 60 studs (≈19 inches) wide. I have not yet measured the actual size of Siebel Center's windows to determine the Lego-to-real life scale.

I started with the western wall of the building since it is a sheer face of brick and windows and I had a picture reference handy. From there, I continued roughly counterclockwise until I reached the glass-faced northern facade which angles out from the main body of the building. I built this pie-shaped section separately and slid it into place against an otherwise blank wall. Many angled "wing" pieces hide the gap.

A patio sits in a depression at the bottom of the angled section. I first attempted to orient the depression to the main building and rest of the courtyard but found that the grass and stairways did not meet the patio nicely. Instead, I connected the slope to the patio and slid the slope under the rest of the grass using stubless plates.

You can view the digital model in Lego Digital Designer. I would love to build the model in real life, but according to LDD, it would cost around $850. I could probably reduce the price by refining the design and buying bulk pieces. Maybe the computer science department could sponsor its construction?

The following images show the completed design. There are more perspectives in the gallery.

Lego Siebel Center facing southeast
Lego Siebel Center facing northeast
Lego Siebel Center facing northwest

Eclipse JUnit Music Box

I wrote an Eclipse plugin that turns Eclipse's built-in JUnit runner into a music box. The following video demonstrates the plugin:

Each test class is assigned one of seven chords in the key of C major. The assignment is deterministic, so a particular sequence of tests will play the same "song". Passing test methods play a pleasing arpeggio, while failing tests play an ugly dissonant chord. The time each test method takes to execute determines the speed of the music. If more than one test class runs, then the music resolves to the tonic at the end of the session.

Here is the plugin (including source code). To try it out, simply save the .jar file in Eclipse's plugins directory and restart Eclipse. I tested it in Eclipse version 3.4.0 running on JDK 6. The plugin requires MIDI, so if you do not hear any sound when running JUnit tests, your computer probably lacks an appropriate MIDI device or it is configured incorrectly. Try running this simple class to test your MIDI setup.

I am not the first to think of making JUnit play music. There is a Musical JUnit project on SourceForge, but it has not been updated in three years. It also uses prewritten samples, while mine produces sound programmatically.

Update Thursday, February 25, 2010

I posted the code on BitBucket.

Cancer Update: Hair Edition

Chemotherapy causes hair loss by damaging fast-growing cells in the hair follicles. I lost the hair on my head very quickly after my first treatment back in September. My eyebrows and eyelashes followed midwinter. I joked with friends that I was constantly surprised throughout the day, but they would never know since I lacked eyebrows.

Guide to facial expressions on a face without eyebrows.

Some of the hair has returned since switching to a new set of drugs. Still no eyebrows, though, and the hair on my head is thin and patchy like a poorly-watered lawn. There is also a strange dark blotch of "normal" hair on the back of my head that a colleague said looked like the start of a Hare Krishna topknot.

Hair splotch

Weird, but no big deal. Baldness is a normal side-effect of chemotherapy, and I will likely keep my head shaved until I finish treatments.

Crunch Time

Every profession has deadlines, but the culture of crunch time is particularly prevalent in high-tech fields. The tendency toward late nights and last-minute sprints is one of the few things I dislike about computer science and programming. Nearly-perfect software takes time.

Even so, I have found myself buried in crunch time more often than I like to admit. Over the last month, for example, I spent many 10-plus-hour days writing my latest research paper. I was curious how this stretch of time compared to previous research papers'. Now that the deadline has passed, I can satisfy my curiosity by examining the papers' revision control system. It contains every edit made to each of the last five papers I was involved in.

The following chart shows how the papers grew as their deadlines approached.

Research paper growth as a deadline approaches

Vertical jumps represent lines added or removed from a paper. Horizontal plateaus represent the length of time between changes. Crunch time is obvious in this diagram: around ten days before a deadline, a paper's growth rate increases, and the length of time between edits decreases. For example, the last ten days of the most recent paper, shown in red, contained about 75% of the paper's total edits.

This number, the fraction of edits in the final ten days, creates an interesting "crunchiness" metric. The following chart shows the metric for each of the papers.

Fraction of edits in the final ten days before a deadline

This chart echoes how I would qualitatively rank the papers: "Automated Testing of Refactoring Engines" was easily the most crunchy and "Predicting Effectiveness of Automatic Testing Tools" was the least.

More research is needed to determine if crunchiness correlates with paper acceptance.

Wanted: eA Ligature

Do any typographers read this weblog? If so, I have a request: I would like an eA ligature for a paper I am writing in LaTeX. Something like the following but in the standard Computer Modern typeface. An italicized version would also be nice.

eA ligature

I need it for a word with InnerCaps that appears throughout the paper. As it is, the separate letters appear too far apart, and the tails do not line up nicely when I play with the kerning. LaTeX already has æ and Æ; why not the other six combinations? Or every two-letter combination? There are only (26 × 2)2 = 2,704 of them.

Related

I now have four library cards

I now have four library cards: Champaign, Indianapolis Central, suburban Indianapolis, and an Indiana public library access card.
Newer Posts Older Posts