PageRenderTime 50ms CodeModel.GetById 1ms app.highlight 42ms RepoModel.GetById 2ms app.codeStats 0ms

/interpreter/tags/at2dist110511/test/edu/vub/at/objects/mirrors/MirrorTest.java

http://ambienttalk.googlecode.com/
Java | 393 lines | 254 code | 35 blank | 104 comment | 1 complexity | c3c8c98d6f14834995350a0066e117c5 MD5 | raw file
  1/**
  2 * AmbientTalk/2 Project
  3 * MirrorTest.java created on Aug 11, 2006 at 11:27:03 PM
  4 * (c) Programming Technology Lab, 2006 - 2007
  5 * Authors: Tom Van Cutsem & Stijn Mostinckx
  6 * 
  7 * Permission is hereby granted, free of charge, to any person
  8 * obtaining a copy of this software and associated documentation
  9 * files (the "Software"), to deal in the Software without
 10 * restriction, including without limitation the rights to use,
 11 * copy, modify, merge, publish, distribute, sublicense, and/or
 12 * sell copies of the Software, and to permit persons to whom the
 13 * Software is furnished to do so, subject to the following
 14 * conditions:
 15 *
 16 * The above copyright notice and this permission notice shall be
 17 * included in all copies or substantial portions of the Software.
 18 *
 19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 20 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 21 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 22 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 23 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 24 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 25 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 26 * OTHER DEALINGS IN THE SOFTWARE.
 27 */
 28
 29package edu.vub.at.objects.mirrors;
 30
 31import edu.vub.at.AmbientTalkTest;
 32import edu.vub.at.eval.Evaluator;
 33import edu.vub.at.exceptions.InterpreterException;
 34import edu.vub.at.exceptions.XSelectorNotFound;
 35import edu.vub.at.exceptions.XAmbienttalk;
 36import edu.vub.at.objects.ATMethod;
 37import edu.vub.at.objects.ATObject;
 38import edu.vub.at.objects.ATTable;
 39import edu.vub.at.objects.coercion.NativeTypeTags;
 40import edu.vub.at.objects.grammar.ATSymbol;
 41import edu.vub.at.objects.natives.NATBoolean;
 42import edu.vub.at.objects.natives.NATNumber;
 43import edu.vub.at.objects.natives.NATTable;
 44import edu.vub.at.objects.natives.grammar.AGSymbol;
 45
 46public class MirrorTest extends AmbientTalkTest {
 47	
 48	public static void main(String[] args) {
 49		junit.swingui.TestRunner.run(MirrorTest.class);
 50	}	
 51	
 52	protected void setUp() throws Exception {
 53		super.setUp();
 54		
 55		ctx_.base_lexicalScope().meta_defineField(AGSymbol.jAlloc("Mirror"), NativeTypeTags._MIRROR_);
 56		ctx_.base_lexicalScope().meta_defineField(AGSymbol.jAlloc("Closure"), NativeTypeTags._CLOSURE_);
 57		ctx_.base_lexicalScope().meta_defineField(AGSymbol.jAlloc("context"), ctx_);
 58		
 59		
 60		evalAndReturn(
 61				"def at := object: { \n" +
 62				"  def mirrors := object: { \n" +
 63				"    def Factory := object: {" +
 64				"       def createMirror(o) { reflect: o }" +
 65				"    }" +
 66				"  }; \n" +
 67				"  def unit := object: { \n" +
 68				"    def XUnitFailed := object: { \n" +
 69				"      def message := \"Unittest Failed\"; \n" +
 70				"      def init(@args) { \n" +
 71				"        if: (args.length > 0) then: { \n" +
 72				"			message := args[1]; \n" +
 73				"        } \n" +
 74				"      } \n" +
 75				"    }; \n" +
 76				"    def fail( message ) { raise: XUnitFailed.new( message ) }; \n" +
 77				"  }; \n" +
 78				"}; \n" +
 79				"\n" +
 80				"def symbol( text ) { jlobby.edu.vub.at.objects.natives.grammar.AGSymbol.alloc( text ) }; \n");
 81	}
 82	
 83	/**
 84	 * Tests to see whether all symbol names defined in 'names' are present in the table of symbols resulting
 85	 * from the evaluation of 'toEval'.
 86	 */
 87	private void evalAndTestContainsAll(String toEval, String[] names) throws InterpreterException {
 88		ATTable methodNames = evalAndReturn(toEval).asTable();
 89		for (int i = 0; i < names.length; i++) {
 90			assertTrue(methodNames.base_contains(AGSymbol.jAlloc(names[i])).asNativeBoolean().javaValue);
 91		}
 92	}
 93	
 94	/**
 95	 * This test invokes all meta-level operations defined on objects and tests whether they 
 96	 * return the proper results. As all these meta-level operations should return mirrors on
 97	 * the 'actual' return values, this test also covers the stratification with respect to
 98	 * return values. A full test of stratified mirror access is provided below.
 99	 */
100	public void testObjectMetaOperations() throws InterpreterException {
101		ATObject subject = evalAndReturn(
102				"def subject := object: { \n" +
103				"  def field := `field; \n" +
104				"  def canonical() { nil }; \n" +
105				"  def keyworded: arg1 message: arg2 { nil }; \n" +
106				"}; \n");
107		evalAndCompareTo(
108				"def mirror := reflect: subject;",
109				"<mirror on:" + subject.toString() + ">");
110		evalAndCompareTo(
111				"mirror.base.super;",
112				Evaluator.getNil());
113		
114		/*evalAndCompareTo( => bad unit test: order of field names is undefined
115				"mirror.listFields();",
116				"[<field:super>, <field:field>]");*/
117		
118		evalAndTestContainsAll("mirror.listFields().map: { |field| field.name };",
119		                   new String[] { "super", "field" });
120		
121		evalAndTestContainsAll("mirror.listMethods().map: { |meth| meth.name };",
122				           new String[] { "keyworded:message:", "canonical" });
123		// methodNames should equal the following table (apart from the ordering of the elements):
124        // [<method:keyworded:message:>, <method:canonical>]
125		
126		evalAndCompareTo(
127				"mirror.eval(context);",
128				subject);
129		evalAndCompareTo(
130				"mirror.quote(context);",
131				subject);
132		evalAndCompareTo(
133				"mirror.print();",
134				"\"" + subject.toString() + "\"");
135		evalAndCompareTo(
136				"mirror.isRelatedTo(nil);",
137				NATBoolean._TRUE_);
138		evalAndCompareTo(
139				"mirror.isCloneOf(object: { nil });",
140				NATBoolean._FALSE_);
141		evalAndCompareTo(
142				"mirror.typeTags;",
143				NATTable.EMPTY);
144		evalAndCompareTo(
145				"mirror.isTaggedAs(Mirror);",
146				NATBoolean._FALSE_);
147
148	}
149
150	/**
151	 * In order to build a full reflective tower, it is necessary to be able to create and use 
152	 * mirrors on mirrors as well. This test covers the creation and use of default introspective
153	 * mirrors on mirrors
154	 */
155	public void testReflectingOnIntrospectiveMirrors() {
156		evalAndCompareTo(
157				"def meta := reflect: false; \n" +
158				"def metaMeta := reflect: meta;",
159				"<mirror on:<mirror on:false>>");
160		evalAndCompareTo(
161				"def select := metaMeta.select(meta, `select)",
162				"<native closure:select>");
163		
164		// this test fails since a select on a mirror first tries to select with a meta prefix
165		// if this fails it performs the select using the nil implementation yet this one does not
166		// take the receiver into account.
167		evalAndCompareTo(
168				"def succeeded := select(false, `not)(); \n",
169				"true");
170
171	}
172	
173	/**
174	 * In order to build a full reflective tower, it is necessary to be able to create and use 
175	 * mirrors on mirrors as well. This test covers the creation and use of default introspective
176	 * mirrors on custom intercessive mirrors
177	 */
178	public void testReflectingOnIntercessiveMirrors() throws InterpreterException {
179		
180		
181		// When they are never instantiated the mirror is a direct child of the mirror root
182		// implying that their base object is the default base object, changes to this object
183		// can corrupt future mirrors. Therefore we explicitly instantiate the mirror which 
184		// clones the ObjectMirrorRoot and gives the mirror its own base object. 
185		ATObject meta = evalAndReturn(
186		//		"def meta := mirror: { nil }; \n");
187		 		"def meta := reflect: (object: { nil } mirroredBy: (mirror: { nil })); \n");
188		assertTrue(meta.meta_isTaggedAs(NativeTypeTags._MIRROR_).asNativeBoolean().javaValue);
189		evalAndCompareTo(
190				"def metaMeta := reflect: meta;",
191				"<mirror on:"+ meta +">");
192		evalAndCompareTo(
193				"def defineField := metaMeta.select(meta, `defineField)",
194				"<native closure:defineField>");
195		evalAndCompareTo(
196				"defineField(`boolValue, true); \n" +
197				"meta.base.boolValue",
198				"true");
199	}
200	
201	/** 
202	 * To uphold stratification, values returned from invocations on a mirror should be 
203	 * automatically wrapped in a mirror. When returning a value that is itself a mirror, this
204	 * property should be upheld as otherwise it is impossible at the meta-level to distinguish
205	 * whether the value of a field is a base or meta-level entity. This test illustrates this
206	 * possible source for confusion.
207	 */ 
208	public void testMirrorWrapping() {
209		evalAndReturn(
210				"def subject := object: { \n" +
211				"  def thisBase := nil; \n" +
212				"  def thisMeta := nil; \n" +
213				"}; \n" +
214				"def mirror := reflect: subject; \n" +
215				"subject.thisBase := subject; \n" +
216				"subject.thisMeta := mirror;");
217		ATObject base = evalAndReturn(
218				"mirror.invoke(subject, .thisBase());");
219		ATObject meta = evalAndReturn(
220				"mirror.invoke(subject, .thisMeta());");
221		
222		assertNotSame(base, meta);
223		assertEquals("<mirror on:"+ base.toString() + ">", meta.toString());
224		
225	}
226	
227	/**
228	 * Tests the correctness of the up-down relation in AmbientTalk : 
229	 * - down(up(o)) == o
230	 */
231	public void testMirrorBaseRelation() {
232		evalAndReturn(
233				"def emptyObject := object: { def getSuper() { super } }; \n" +
234				"def objects := [ nil, true, 1, emptyObject, emptyObject.getSuper(), reflect: nil, mirror: { nil } ]; \n" +
235				"def mirrors[objects.length] { nil }; \n" +
236				"\n" +
237				"1.to: objects.length do: { | i | \n" +
238				"  mirrors[i] := reflect: objects[i]; \n" +
239				"}; \n" +
240				"1.to: objects.length do: { | i | \n" +
241				"  (objects[i] == mirrors[i].base) \n" +
242				"    .ifFalse: { at.unit.fail(\"down(up(\" + objects[i] + \")) != \" + objects[i]); } \n" +
243				"} \n");
244	}
245			
246	public void testMirrorInvocation() {
247		evalAndReturn(
248				"def trueMirror  := at.mirrors.Factory.createMirror(true);" +
249				"def responds    := trueMirror.respondsTo( symbol( \"ifTrue:\" ) );" +
250				"responds.ifFalse: { at.unit.fail(\"Incorrect Mirror Invocation\"); }");
251	}
252			
253	public void testMirrorFieldAccess() {
254		evalAndReturn(
255				"def basicClosure := { raise: (object: { def [ message, stackTrace ] := [ nil, nil ] }) }; \n" +
256				"def extendedMirroredClosure := \n" +
257				"  object: { super := basicClosure } \n" +
258				"    taggedAs: [ Closure ] \n" +
259				"    mirroredBy: (mirror: { nil }); \n"  +
260				"def intercessiveMirror := \n" +
261				"  reflect: extendedMirroredClosure");
262		
263		evalAndTestException(
264				"intercessiveMirror.base.super.apply([]); \n",
265				XAmbienttalk.class);
266
267//		Can no longer set the mirror of a mirage the final 1-1 mapping is now stricly enforced
268//		// Cannot assign a base-level entity to a meta-level variable
269//		evalAndTestException(
270//				"intercessiveMirror.mirror := \n" +
271//				"  object: { \n" +
272//				"    def invoke(@args) { reflect: \"ok\"}; \n" +
273//				"  };\n" +
274//				"extendedMirroredClosure.whatever()",
275//				XTypeMismatch.class);
276//		
277//		// Cannot assign a base-level entity to a meta-level variable
278//		evalAndReturn(
279//				"intercessiveMirror.mirror := \n" +
280//				"  mirror: { \n" +
281//				"    def invoke(@args) { reflect: \"ok\"}; \n" +
282//				"  };\n" +
283//				"extendedMirroredClosure.whatever()");
284	}
285	
286	/**
287	 * This test tests the listMethods meta operations and ensures the following properties:
288	 * Empty objects contain three primitive methods: namely new, init and ==. These methods
289	 * can be overridden with custom behaviour which shadows the primitive implementation.
290	 * Also external method definitions are closures which properly inserted into the table 
291	 * of methods. Whether an object has a intercessive or introspective mirror does not matter
292	 * unless the intercessive mirror intercepts the listMethods operation.
293	 */
294	public void testListMethods() throws InterpreterException {
295		
296		evalAndTestContainsAll(
297				"def test := object: { }; \n" +
298				"(reflect: test).listMethods().map: { |meth| meth.name }; \n",
299				new String[] { });
300		evalAndTestContainsAll(
301				"def testMirrored := object: { nil } mirroredBy: (mirror: { nil }); \n" +
302				"(reflect: testMirrored).listMethods().map: { |meth| meth.name }; \n",
303				new String[] { });
304		evalAndTestContainsAll(
305				"test := object: { \n" +
306				"  def init(); \n" +
307				"}; \n" +
308				"(reflect: test).listMethods().map: { |meth| meth.name }; \n",
309				new String[] { "init" });
310		evalAndTestContainsAll(
311				"testMirrored := object: { \n" +
312				"  def init(); \n" +
313				"} mirroredBy: (mirror: { nil });  \n" +
314				"(reflect: testMirrored).listMethods().map: { |meth| meth.name }; \n",
315				new String[] { "init" });
316		evalAndTestContainsAll(
317				"def test.hello() { \"hello world\" }; \n" +
318				"(reflect: test).listMethods().map: { |meth| meth.name }; \n",
319				new String[] { "hello" });
320		evalAndTestContainsAll(
321				"def testMirrored.hello() { \"hello world\" }; \n" +
322				"(reflect: test).listMethods().map: { |meth| meth.name }; \n",
323				new String[] { "hello" });
324	}
325	
326	/**
327	 * Following Bug report 0000001: Test whether the correct selector is returned when invoking 
328	 * meta_operations on a custom mirror which themselves may throw XSelectorNotFound exceptions.
329	 * @throws InterpreterException
330	 */
331	public void testNotFoundReporting() throws InterpreterException {
332		// The meta operation select will be found, but the field x will not be found.
333		try {
334			evalAndThrowException(
335					"def emptyMirror := mirror: { nil }; \n" +
336					"def emptyObject := object: { nil }; \n" +
337					"emptyObject.x;",
338					XSelectorNotFound.class);
339		} catch (XSelectorNotFound e) {
340			assertEquals(e.getSelector(), AGSymbol.jAlloc("x"));
341		}
342		// Intentionally misspelled the name of a meta operation to test whether failures to
343		// find meta operations are still properly reported.
344		try {
345			evalAndThrowException(
346					"def emptyObject.x := 42; \n" +
347					"emptyMirror.selct(emptyObject, `x);",
348					XSelectorNotFound.class);
349		} catch (XSelectorNotFound e) {
350			assertEquals(e.getSelector(), AGSymbol.jAlloc("selct"));
351		}
352	}
353	
354	public void testMethodSlotRemoval() throws InterpreterException {
355		ATSymbol m_sym = AGSymbol.jAlloc("m");
356		ATObject obj = evalAndReturn("def removalObj := object: { def m(); }");
357		assertTrue(obj.meta_respondsTo(m_sym).asNativeBoolean().javaValue);
358		assertEquals(3, obj.meta_listSlots().base_length().asNativeNumber().javaValue);
359		ATMethod slotVal = obj.meta_removeSlot(m_sym).asMethod();
360		assertEquals(m_sym, slotVal.base_name());
361		evalAndTestException("removalObj.m()", XSelectorNotFound.class);
362		assertEquals(2, obj.meta_listSlots().base_length().asNativeNumber().javaValue);
363	}
364	
365	public void testFieldSlotRemoval() throws InterpreterException {
366		ATSymbol x_sym = AGSymbol.jAlloc("x");
367		ATObject obj = evalAndReturn("def removalObj := object: { def x := 1; }");
368		assertTrue(obj.meta_respondsTo(x_sym).asNativeBoolean().javaValue);
369		assertEquals(4, obj.meta_listSlots().base_length().asNativeNumber().javaValue);
370		assertEquals(NATNumber.ONE, obj.meta_removeSlot(x_sym));
371		evalAndTestException("removalObj.x", XSelectorNotFound.class);
372		assertEquals(2, obj.meta_listSlots().base_length().asNativeNumber().javaValue);
373	}
374	
375	public void testCustomFieldSlotRemoval() throws InterpreterException {
376		ATSymbol x_sym = AGSymbol.jAlloc("x");
377		ATObject obj = evalAndReturn("def removalObj := object: { }");
378		obj.meta_addField(evalAndReturn("" +
379				"deftype Field;" +
380				"object: {" +
381				"  def name := `x;" +
382				"  def readField() { 1 };" +
383				"  def writeField(newx) { };" +
384				"  def accessor() { (&readField).method };" +
385				"  def mutator() { (&writeField).method };" +
386				"} taggedAs: [Field]").asField());
387		assertTrue(obj.meta_respondsTo(x_sym).asNativeBoolean().javaValue);
388		assertEquals(4, obj.meta_listSlots().base_length().asNativeNumber().javaValue);
389		assertEquals(NATNumber.ONE, obj.meta_removeSlot(x_sym));
390		evalAndTestException("removalObj.x", XSelectorNotFound.class);
391		assertEquals(2, obj.meta_listSlots().base_length().asNativeNumber().javaValue);
392	}
393}