jeudi 15 décembre 2016

How to record and play back randoms in Java?

I am working on an application which includes a generation phase where a set of data is generated. The generation involves a number of loops, some of which generate random numbers in order to make decisions of how to proceed. This means the generated set of data is not the same every time.

I am trying to track down some bugs which occur only under specific conditions with the set of generated data. Given a buggy dataset, I know why the bugs occur in the application, but what I'm trying to troubleshoot is how the generation phase got to generate the buggy dataset in the first place. However this is not easy to debug because 99% of the time the generation phase does not generate a buggy dataset.

Due to this, I wanted to have a way to record and playback the numbers which are being generated, to reproduce the specific conditions under which the bugs occur. I have searched for such a framework online but I have not come up with anything. Unfortunately "record randoms" returns mostly results about random records, whereas "repeatable randoms" returns mostly results about non-repeatable RNGs!

I know that the code in the generation phase only ever calls the java.util.Random.nextInt(int) method which simplifies the problem somewhat. I started to think about creating a repeatable random framework myself.

public class RepeatableRandom extends Random {
    private static final Object LOCK = new Object();
    private static final Random RANDOM = new Random();

    private static enum Mode { PASSTHROUGH, RECORD, PLAYBACK; }
    private static Mode mode = Mode.PASSTHROUGH;
    // ...synchronized setters for each of the three modes...

    private static Map<Integer,Integer> invocationCount = new HashMap<>();
    private static Map<Integer,List<Integer>> randoms = new HashMap<>();

    @Override
    public int nextInt(int n) {
        switch (mode) {
            case Mode.PASSTHROUGH:
                return RANDOM.nextInt(n);
            case Mode.RECORD:
                if (!randoms.containsKey(n)) {
                    randoms.put(n,new ArrayList<>());
                }
                int nextInt = RANDOM.nextInt(n);
                randoms.get(n).put(nextInt);
                return nextInt;
            case Mode.PLAYBACK:
                synchronized(LOCK) {
                    int i = invocationCount.get(n);
                    int nextInt = randoms.get(n).get(invocationCount);
                    invocationCount.put(n,i+1);
                    return nextInt;
                }
        }
    }

    public void loadRandomsFromFile(String filename) { //... }
    public void saveRandomsToFile(String filename) { //... }
    public void clear() { // ... }
}

After replacing all references to Random in the generation phase with RepeatableRandom I then would write test code like the following:

RepeatableRandom.setRecordMode();
do {
    RepeatableRandom.clear();
    dataSet = generateDataSet();
while (!isBuggyDataSet(dataSet));
RepeatableRandom.saveRandomsToFile(FILENAME);

Later, in my code I can then write:

RepeatableRandom.setPlaybackMode();
RepeatableRandom.clear();
RepeatableRandom.loadRandomsFromFile(FILENAME);
executeMainApplication();

I can then run in debug mode with a load of breakpoints and finally figure out why this specific buggy set of data is generated.

However I feel sure I am not the first person to have faced this problem. And I don't feel comfortable with creating a whole new bespoke framework just for this use case. I feel there must be something already out there but I just can't find it! What is the correct approach to solve this kind of problem?




Aucun commentaire:

Enregistrer un commentaire