Smalltalk: How to Change Your Own Behavior

I am migrating my favorite Mocktito library to Java / JavaScript in Smalltalk. I'm currently in the Spy implementation phase to crop real objects. My problem arises when the spy object calls its own method, which is hatched. Instead:

self aMethod. 

I would prefer to delegate the call to the spy object:

 spyObject aMethod. 

Here is the scenario for the expected behavior:

 realObject := RealObjectForTesting new. spyedObject := Spy new: realObject. spyedObject when: #accesorWhichReturnsValue thenReturn: 'stubbed value'. spyedObject accesorWhichCallsSelf. self assert: (spyedObject verify: #accesorWhichReturnsValue). 

Any suggestion?

+4
source share
3 answers

You make your spy a wrapper object that implements only doesNotUnderstand: and then replaces it for the real object with become:

The spy method doesNotUnderstand: will be called for all messages, and you can then, for example, log its argument (which is the message object) and send it to the original object.

If you're looking at doesNotUnderstand: artists in the Smalltalk image, you can find a few examples (for example, Squeak has MessageCatcher and ObjectViewer ).

+3
source

You can directly link RealObjectUnderTest CompiledMethod using the ObjectsAsMethodsWrapper library . This provides a convenient API for installing and removing shells along with some handy predefined shells.

They will intercept self-sending, since the wrappers are installed in the dictionary of real objects and, thus, can make arbitrary changes to the message before passing it to the base CompiledMethod .

While my example shows how to memoize a method call without touching the source code, it should provide you with the basic knowledge needed to retrieve method calls.

There is a limitation in this particular method: it intercepts self-sends messages that the class itself defines. Therefore, if Foo subclasses Bar and you install wrappers in Foo , you will not intercept messages that form part of the Bar protocol (unless, of course, you wrap them).

You will not be able to intercept ifTrue:ifFalse: timesRepeat or similar messages in a Squeak or Pharo image (and probably in GNU Smalltalk too), because these are not message sendings: compile-time transformations built into this message send in transfer byte codes. (The illusion of sending a message is relatively convincing, because Decompiler knows how not to convert bytecodes back to ifTrue:ifFalse: or something else.)

+3
source

Smalltalk does not have a built-in mechanism to intercept shipments for itself, so you have to resort to exotic measures.

Probably the easiest way would be to dynamically steal methods from the original object. Your spy will replace the target using #become :, as Bert suggests. Then, when the spy receives the message, instead of forwarding the message to the original object, you look at the selector in your class and execute it with the spy as the recipient. The execution mechanism of the compiled method against an arbitrary receiver will vary from dialect to dialect. In Squeak, this is CompiledMethod class → receiver: withArguments: executeMethod :.

As I said, this is quite exotic - you need to create a custom class for the spy when you create it and make sure that it has the same structure as the target object, so that the compiled methods will work correctly with the new receiver. You will also have to copy the state of the target to the spy and return to the orignal object when you finish spying. Finally, you will need to find a place to hide the target, since the state of the spy must match the state of the target. All this is doable, but it is not easy. You will dynamically generate classes and methods that require a low level of understanding of the system.

+2
source

Source: https://habr.com/ru/post/1443166/


All Articles