Changing source code from tests

Right now I work a lot with code generation and reflectivity and often I need to change source code of a class to test some behavior.

But if I change it, then I also need to change it back, which is annoying. So how to go about it?

Naive - fix it at the end of the test

1
2
3
4
5
6
7
8
9
TestClass>>testRecompile
"remember the old source code"
oldCode := (SomeObject>>#name) sourceCode.
SomeObject compile: 'name ... some new code ...'.
"… do some tests …".
SomeObject compile: oldCode.

Can you see any problem with this? If one of the tests fails then I lose the code.

Delay it for tearDown

I could use #tearDown instead and have some mechanism around.

1
2
3
4
5
6
7
8
9
10
11
TestClass>>testRecompile
self ensureRestored: (SomeObject>>#name).
"… do some tests …"
TestClass>>ensureRestored: aMethod
toRestore add: aMethod methodClass -> aMethod sourceCode
TestCase>>tearDown
toRestore do: [ :pair |
pair key compile: pair value
]

Although extra work is needed around, in the test method itself I need just a single extra line, which I like.

Use an anonymous subclass

Alternatively I could just create an anonymous subclass, do whatever I want with it and then not care about it.

1
2
3
4
5
6
7
TestClass>>testRecompile
anonymousClass := SomeObject newAnonymousSubclass.
"SomeObject's source code is unaffected by this"
anonymousClass compile: 'name …'.
"and if I already have an instance of SomeObject, I can just warp it"
someObjectInstance primitiveChangeClassTo: anonymousClass new.

What is primitiveChangeClassTo: for? To change the class of an object without reinitialization or anything. Be warned though, you can’t just warp between random classes as they have different attributes and layout. But anonymous subclasses, and parent classes should generally be fine.

1
2
3
4
5
6
7
8
9
obj := MTTestInstallTarget new.
obj className. "'MTTestInstallTarget'"
obj primitiveChangeClassTo: MTTestInstallTarget newAnonymousSubclass new.
obj className. "'a subclass of MTTestInstallTarget'"
obj primitiveChangeClassTo: MTTestInstallTarget new.
obj className. "'MTTestInstallTarget'"

Which approach is the best? Certainly not the first one.

I prefer the last one the most as I don’t really need to worry about fixing things — I’ve never broken anything to begin with! But if I don’t control the instances of the original class (i.e. I can’t change their class), then I have no choice but to use the second one.