How to implement instance behaviour (for testing) in Cuis/Squeak/Pharo?

134 views Asked by At

I've implemented a few ExternalStrctures (as part of an "FFI effort"), and for some of them I want to implement finalization for reclaiming the external memory.

I'm trying to write some tests for that, and thought a good way to know if #finalize is called is to change the behaviour for the particular instance I'm using for testing. I'd rather not pollute the implementation with code for supporting tests if possible.

I believe mocking specific methods and changing specific instance behavior is in general a good tool for testing.

I know it's possible in other dialects, and I've implemented it myself in the past in Squeak using #doesNotUnderstand, but I'd like to know if there's a cleaner way, possibly supported by the VM.

Is there a way to change how a particular instance answers a particular message in Cuis/Squeak/Pharo?

2

There are 2 answers

5
Juan Vuletich On BEST ANSWER

Luciano gave this wonderful example:

EllipseMorph copy compile: 'defaultColor ^Color red'; new :: openInWorld

The mail thread is here: http://cuis-smalltalk.org/pipermail/cuis-dev_cuis-smalltalk.org/2016-March/000458.html

2
gera On

After dealing with the problem I decided to go for an end to end test, actually verifying the resource (memory in my case) is restored to the system. I had not used instance behavior, though Luciano's and Juan's solution (in a comment) is very interesting. Here's the code I'm using for testing:

testFinalizationReleasesExternalMemory
    " WeakArray restartFinalizationProcess "
    | handles |

    handles := (1 to: 11) collect: [:i |
        Smalltalk garbageCollect.
        APIStatus create getHandle].

    self assert: (handles asSet size) < 11.

In the example, #create uses an FFI call to an external function that allocates memory and returns a pointer (the name create comes from the external API):

create
    | answer |
    answer := ExternalAPI current createStatus.
    self finalizationRegistry add: answer.
    ^ answer

ExternalAPI here is the FFI interface, #createStatus is the API call that allocates the memory for an APIStatus and returns a pointer to it.

On finalization I call the API which restores the memory:

delete
    self finalizationRegistry remove: self ifAbsent: [].
    self library deleteStatus: self.
    handle := nil.

Where #deleteStatus: is again the API call which frees the memory.

The test assumes that the external library reuses the memory once it's free, specially when the newly allocated block has the same size of the previous. This is correct in most cases today, but I'd like to see this test failing if it's not, if at least just to learn something new.

The test allocates 11 external structures, saves their pointers, leaves the finalization mechanism free the memory of each one before allocating the next, and then compares whether any of the pointers is repeated. I'm not sure why I decided to use 10 pointers as a good number, just 2 should be enough, but memory allocation algorithms are sometimes tricky.