junit-quickcheck icon indicating copy to clipboard operation
junit-quickcheck copied to clipboard

Allow deallocation of generated values

Open spodkowinski opened this issue 9 years ago • 9 comments

Allocating new values by calling Generator<T>.generate() will create new object instances for each trial and shrink iteration. This is working just fine for simple objects, but will not allow you to destroy objects that have been allocating external resources. E.g. a FileGenerator may want to delete any generated files after the generated value is not needed anymore. There should be a way to tear down more complex generated values.

Possible approaches that come to my mind would be:

  1. Add destroy(Object) method to Generator that must be called with any object created by generate(). Simple to implement, but contract will be hard to enforce.
  2. Allow to configure an optional GenerationCollector implementation that will receive all objects that have been generated during PropertyStatement.verifyProperty() after the method returned. The GenerationCollector implementation would have to figure out which resources need to be deallocated and how, e.g. obj instanceof File && obj.delete(). The hard part would be how to track which objects have been generated.
  3. Do not offer any contract for this at all, but allow generators to be notified whenever a PropertyStatement.verifyProperty() iteration has finished. The generator could temporary store references to any resources that need to be deallocated and free them after each iteration.

spodkowinski avatar Mar 10 '16 13:03 spodkowinski

@spodkowinski Thanks for this -- sorry I'm so late in responding.

I am leaning toward option 1 above. Will experiment with it a bit and see how it works.

pholser avatar Aug 31 '16 01:08 pholser

@spodkowinski The tricky part, I think, will be how to handle generated objects that never make it to a PropertyVerifier. For example:

  • In the presence of a @When(satisfies = condition) annotation, values may get generated for a parameter but not be accepted.
  • Values produced by a generator outfitted with Gen.filter() -- same issue.

Maybe not so bad -- let me bang on it some more.

pholser avatar Aug 31 '16 18:08 pholser

@spodkowinski Should a "disposal" swallow any exceptions that it may raise? Log them?

pholser avatar Aug 31 '16 18:08 pholser

@spodkowinski I'm beginning to wonder whether or not leaving this capability out hinders the functionality of junit-quickcheck enough to warrant adding the additional complexity.

What workarounds are there? Property methods that receive generated objects that claim external resources could dispose of them, at the cost of some extra code in the test class.

pholser avatar Aug 31 '16 18:08 pholser

Perhaps this and gh-113 are two sides of the same coin. Allowing for an @Before and @After type construct that accepts parameters might solve the problem. It might make sense to have the @After be given the same values as the @Before, rather than generating new ones.

m0smith avatar Sep 12 '16 01:09 m0smith

@spodkowinski @m0smith One option might be to create a Rule that can hold values to be deallocated:

    public static class FileDisposer extends ExternalResource {
        private final List<File> candidates = new ArrayList<>();

        public void collect(File first, File... rest) {
            candidates.add(first);
            Collections.addAll(candidates, rest);
        }

        @Override protected void after() {
            candidates.forEach(File::delete);
        }
    }

    @RunWith(JUnitQuickcheck.class)
    public static class FileProperties {
        @Rule public final FileDisposer d = new FileDisposer();
        
        @Property public void holds(File f) {
            d.collect(f);
            
            // ...
        }
    }

The cost to you, the programmer, is the creation of the Rule and the collecting of the values you want disposed in the property method itself. Cost to junit-quickcheck: no additional code needed, since junit-quickcheck honors Rules and other such JUnit machinery.

Let me know what you think.

pholser avatar Dec 29 '16 19:12 pholser

Would the rule be called on each trial or just after the property method evaluation finished? What about the mentioned values that won't make it into the test method as there are filtered out?

spodkowinski avatar Jan 02 '17 14:01 spodkowinski

@pholser I like the idea of leveraging Rule annotated instances to do this as it is part of junit already. An example in the documentation would be great as I wasn't even aware of the Rule annotation until you pointed it out.

I also think that having a Rule with Prime methods (see comments in gh-113) would be a good addition sometime in the future.

m0smith avatar Jan 02 '17 16:01 m0smith

@spodkowinski The rule would wrap each trial, including shrinks. Thus, its after method would follow every trial.

The rule won't catch any values filtered by the methods mentioned above.

pholser avatar Jan 03 '17 16:01 pholser