PageRenderTime 71ms CodeModel.GetById 16ms app.highlight 47ms RepoModel.GetById 1ms app.codeStats 1ms

/interpreter/tags/at2dist090708/test/edu/vub/at/objects/natives/UniformAccessTest.java

http://ambienttalk.googlecode.com/
Java | 441 lines | 237 code | 48 blank | 156 comment | 0 complexity | 3fa282f2b75f5d98c72656d0c07d6799 MD5 | raw file
  1package edu.vub.at.objects.natives;
  2
  3import edu.vub.at.AmbientTalkTest;
  4import edu.vub.at.actors.ATAsyncMessage;
  5import edu.vub.at.eval.Evaluator;
  6import edu.vub.at.exceptions.InterpreterException;
  7import edu.vub.at.exceptions.XArityMismatch;
  8import edu.vub.at.exceptions.XUnassignableField;
  9import edu.vub.at.objects.ATClosure;
 10import edu.vub.at.objects.ATField;
 11import edu.vub.at.objects.ATObject;
 12import edu.vub.at.objects.ATTable;
 13import edu.vub.at.objects.coercion.NativeTypeTags;
 14import edu.vub.at.objects.mirrors.NativeClosure;
 15import edu.vub.at.objects.mirrors.Reflection;
 16import edu.vub.at.objects.natives.grammar.AGMessageSend;
 17import edu.vub.at.objects.natives.grammar.AGMethodInvocationCreation;
 18import edu.vub.at.objects.natives.grammar.AGSymbol;
 19
 20/**
 21 * Unit test class to verify whether the uniform access principle is upheld both for invocations and lexical
 22 * function calls. This test covers both the invocation and selection (resp. call and lookup) operations.
 23 * 
 24 * @author smostinc
 25 */
 26public class UniformAccessTest extends AmbientTalkTest {
 27	
 28	/**
 29	 * Used to run this test suite independently of the InterpreterTests Suite
 30	 */
 31	public static void main(String[] args) {
 32		junit.swingui.TestRunner.run(UniformAccessTest.class);
 33	}
 34	
 35	/**
 36	 * Constructor which installs the <tt>withScope: scope do: closure</tt> construct in the 
 37	 * scope in which tests are executed. 
 38	 */
 39	public UniformAccessTest() throws InterpreterException {
 40		super(); // sets up the ctx_ properly
 41		
 42		ctx_.base_lexicalScope().meta_defineField(AGSymbol.jAlloc("withScope:do:"), atWithScope_Do_);
 43	}
 44	
 45	private final NATNumber atThree_ = NATNumber.atValue(3);
 46	private final AGSymbol atX_ = AGSymbol.alloc(NATText.atValue("x"));
 47	private final AGSymbol atY_ = AGSymbol.alloc(NATText.atValue("y"));
 48	private final AGSymbol atM_ = AGSymbol.alloc(NATText.atValue("m"));
 49	
 50	/**
 51	 * Auxiliary construct to be used in the test suite to execute a closure within the
 52	 * scope of a given scope object. This is used to test the semantics of call and 
 53	 * lookup in particularly in combination with native and symbiotic objects. 
 54	 */
 55	private final ATClosure atWithScope_Do_ = new NativeClosure(Evaluator.getNil()) {
 56		public ATObject base_apply(ATTable arguments) throws InterpreterException {
 57			ATObject closureScope = arguments.base_at(NATNumber.ONE);
 58			ATClosure literalClosure = arguments.base_at(NATNumber.atValue(2)).asClosure();
 59			
 60			return literalClosure.base_method().base_wrap(closureScope, literalClosure.base_context().base_receiver())
 61			  .base_apply(NATTable.EMPTY);
 62		}
 63	};
 64		
 65	/**
 66	 * Tests whether the invoke operation treats fields (such as the length of a table) 
 67	 * and nullary methods (to e.g. compute the absolute value of a number) equally
 68	 * when invoked upon native data types.
 69	 * 
 70	 * The invocation is performed both using a field acessing and a canonical invocation
 71	 * syntax, and illustrates that both are equivalent.
 72	 */
 73	public void testUniformInvokeOnNatives() throws InterpreterException {
 74		// (-1).abs -> (-1).abs() -> 1
 75		// [1, 2, 3].length -> [1, 2, 3].length() -> 3
 76		evalAndCompareTo("(-1).abs", NATNumber.ONE);
 77		evalAndCompareTo("(-1).abs()", NATNumber.ONE);
 78		evalAndCompareTo("[1, 2, 3].length", atThree_);
 79		evalAndCompareTo("[1, 2, 3].length()", atThree_);
 80	}
 81	
 82	/**
 83	 * Tests whether the call operation treats fields (such as the length of a table) 
 84	 * and nullary methods (to e.g. compute the absolute value of a number) equally
 85	 * when invoked upon native data types.
 86	 * 
 87	 * The call is performed both using a field acessing and a canonical invocation
 88	 * syntax, and illustrates that both are equivalent.
 89	 */
 90	public void testUniformCallOnNatives() throws InterpreterException {
 91		// (-1).abs -> (-1).abs() -> 1
 92		// [1, 2, 3].length -> [1, 2, 3].length() -> 3
 93		evalAndCompareTo("withScope: (-1) do: { abs }", NATNumber.ONE);
 94		evalAndCompareTo("withScope: (-1) do: { abs() }", NATNumber.ONE);
 95		evalAndCompareTo("withScope: [1, 2, 3] do: { length }", atThree_);	
 96		evalAndCompareTo("withScope: [1, 2, 3] do: { length() }", atThree_);	
 97	}
 98	
 99	/**
100	 * Tests the uniform selection of both fields and methods from native data types. The
101	 * selection is uniform in the sense that both return closures which can subsequently be
102	 * applied. 
103	 * 
104	 * When selecting a field, the resulting closure is in fact an implictly created accessor
105	 * which provides access to the "current" value of the field, not the one when the field
106	 * was selected.
107	 */
108	public void testUniformSelectionOnNatives() throws InterpreterException {
109		// (-1).&abs -> <native impl> <+ apply([]) -> 1
110		// [1, 2, 3].&length -> <native impl> <+ apply([]) -> 3
111		evalAndCompareTo("def abs := (-1).&abs", "<native closure:abs>");
112		evalAndCompareTo("def len := [1, 2, 3].&length", "<native closure:length>");
113		evalAndCompareTo("abs()", NATNumber.ONE);
114		evalAndCompareTo("len()", atThree_);
115		
116		// selection gives up to date info, not stale one recorded at selection time
117		// first we select an accessor for the receiver exp of an invocation
118		evalAndReturn("def x := `(o.m()); \n" +
119				      "def receiver := x.&receiverExpression");
120		// subsequently assign the receiver expression with a new value
121		evalAndReturn("x.receiverExpression := `object");
122		// finally assert that the new value is correctly reported
123		evalAndCompareTo("receiver()", AGSymbol.jAlloc("object"));
124	}
125	
126	/**
127	 * Tests the uniform lookup of both variables and functions in native data types. The
128	 * lookup is uniform in the sense that both return closures which can subsequently be
129	 * applied. 
130	 * 
131	 * When looking up a variable, the resulting closure is in fact an implictly created 
132	 * accessor which provides access to the "current" value of the variable, not the one 
133	 * when the accessor was created.
134	 */
135	public void testUniformLookupOnNatives() throws InterpreterException {
136		// (-1).&abs -> <native impl> <+ apply([]) -> 1
137		// [1, 2, 3].&length -> <native impl> <+ apply([]) -> 3
138		evalAndCompareTo("def abs := withScope: (-1) do: { &abs }", "<native closure:abs>");
139		evalAndCompareTo("def len := withScope: [1, 2, 3] do: { &length }", "<native closure:length>");
140		evalAndCompareTo("abs()", NATNumber.ONE);
141		evalAndCompareTo("len()", atThree_);
142		
143		// lookup gives up to date info, not stale one recorded at lookup time
144		// first we create an accessor for the receiver exp of an invocation
145		evalAndReturn("def x := `(o.m()); \n" +
146				      "def receiver := withScope: x do: { &receiverExpression }");
147		// subsequently assign the receiver expression with a new value
148		evalAndReturn("x.receiverExpression := `object");
149		// finally assert that the new value is correctly reported
150		evalAndCompareTo("receiver()", AGSymbol.jAlloc("object"));
151	}
152	
153	/**
154	 * Tests lexical field mutator access.
155	 */
156	public void testLexicalMutatorAccess() throws InterpreterException {
157		evalAndReturn("def testobj := object: { def x := 5; def m() { nil }; def c := { 5 } }");
158		evalAndCompareTo("withScope: testobj do: { &x:= }", "<native closure:x:=>");
159		// there is no implicit mutator for nullary methods
160		evalAndTestException("withScope: testobj do: { &m:= }", XUnassignableField.class);
161		evalAndCompareTo("withScope: testobj do: { &c:= }", "<native closure:c:=>");
162	}
163	
164	/**
165	 * The correctness of assignments on native data types is verified by the four previous tests in
166	 * the course of testing whether the returned accessors return up to date information rather than
167	 * stale one recored when the accessor was created.
168	 * 
169	 * This test does not rely on the correct functioning of the accessor but instead manually checks 
170	 * the output of the implementation-level accessor and is therefore complementary to the previous
171	 * tests as it allows determining whether the semantics of assignment or of the created accessor
172	 * are incorrect.
173	 */
174	public void testAssignmentOnNatives() throws InterpreterException {
175		AGMethodInvocationCreation msgExp = new AGMethodInvocationCreation(
176				/* selector = */	atM_, 
177				/* arguments = */	NATTable.EMPTY, 
178				/* annotations = */	NATTable.EMPTY);
179		AGMessageSend sendExp= new AGMessageSend(
180				/* receiver = */	atX_,
181				/* message = */		msgExp);
182		ctx_.base_lexicalScope().meta_defineField(AGSymbol.jAlloc("sendExp"), sendExp);
183
184		evalAndReturn("sendExp.receiverExpression := `y");
185		assertEquals(sendExp.base_receiverExpression(), atY_); 
186	}
187
188	/**
189	 * The amalgamation of fields and methods by the uniform access principle allows the definition
190	 * of setter methods which can be used as if assigning ordinary fields. This test verifies that
191	 * the assignment of two fields (one standard field and one implemented with a custom accessor
192	 * and mutator pair) can be performed in an identical fashion.
193	 */
194	public void testUniformAssignmentOnObjects() {
195		evalAndReturn(
196				"def time := object: { \n" +
197				"  def elapsed := 12752; \n" +
198				"\n" +
199				"  def seconds() { (elapsed %  60) }; \n" +
200				"  def minutes() { (elapsed /- 60) % 60 }; \n" +
201				"  def hours()   { (elapsed /- 3600) }; \n" +
202				"\n" +
203				"  def seconds:=(newSeconds) { \n" +
204				"    elapsed := elapsed - seconds + newSeconds; \n" +
205				"    newSeconds \n" +
206				"  }; \n" +
207				"  def minutes:=(newMinutes) { \n" +
208				"    elapsed := elapsed - (minutes * 60) + (newMinutes * 60); \n" +
209				"    newMinutes \n" +
210				"  }; \n" +
211				"  def hours:=(newHours) {  \n" +
212				"    elapsed := elapsed - (hours * 3600) + (newHours * 3600); \n" +
213				"    newHours \n" +
214				"  }; \n" +
215				"}");
216		evalAndCompareTo("time.hours", atThree_);
217		evalAndCompareTo("time.hours := 1", NATNumber.ONE);
218		
219		evalAndCompareTo("time.elapsed", "5552");
220		evalAndCompareTo("time.elapsed := 12752", "12752");
221		
222		evalAndCompareTo("withScope: time do: { hours }", atThree_);
223		evalAndCompareTo("withScope: time do: { hours := 1 }", NATNumber.ONE);
224		
225		evalAndCompareTo("withScope: time do: { elapsed }", "5552");
226		evalAndCompareTo("withScope: time do: { elapsed := 12752 }", "12752");
227	}
228	
229	/**
230	 * Tests both field and variable access as well as method invocation and function calls occurring
231	 * in the scope of a symbiotic JavaObject wrapper. Both the canonical application and variable access 
232	 * syntax are tested.
233	 * 
234	 * This test uses both the size method and the elementCount java field to illustrate that both can
235	 * be used interchangibly in AmbientTalk.
236	 */
237	public void testUniformAccessOnSymbionts() {
238		// use a variant of vector which has a public field instead of a protected one
239		evalAndReturn("def jVector := jlobby.edu.vub.at.objects.natives.VectorProxy.new()");
240		
241		evalAndCompareTo("jVector.size", NATNumber.ZERO);
242		evalAndCompareTo("jVector.size()", NATNumber.ZERO);
243		evalAndCompareTo("jVector.elementCount", NATNumber.ZERO);
244		evalAndCompareTo("jVector.elementCount()", NATNumber.ZERO);
245		
246		evalAndCompareTo("withScope: jVector do: { size }", NATNumber.ZERO);
247		evalAndCompareTo("withScope: jVector do: { size() }", NATNumber.ZERO);
248		evalAndCompareTo("withScope: jVector do: { elementCount }", NATNumber.ZERO);
249		evalAndCompareTo("withScope: jVector do: { elementCount() }", NATNumber.ZERO);
250	}
251	
252	/**
253	 * Tests both the selection and lookup of both methods and implict field accessors in the scope of 
254	 * a symbiotic JavaObject wrapper. Both the canonical application and variable access syntax are tested.
255	 * 
256	 */
257	public void testUniformSelectionOnSymbionts() {
258		evalAndReturn("def jVector := jlobby.edu.vub.at.objects.natives.VectorProxy.new()");
259		
260		evalAndCompareTo(
261				"def selSize := jVector.&size", 
262				"<java closure:size>");
263		evalAndCompareTo(
264				"def lexSize := \n" +
265				  "withScope: jVector do: { &size }", 
266				"<java closure:size>");
267		evalAndCompareTo(
268				"def selElementCount := jVector.&elementCount", 
269				"<native closure:elementCount>");
270		evalAndCompareTo(
271				"def lexElementCount := \n" +
272				  "withScope: jVector do: { &elementCount }", 
273				"<native closure:elementCount>");
274		
275		evalAndReturn("jVector.add( [4, 8, 15, 16, 23, 42] )");
276		
277		evalAndCompareTo("selSize",   "<java closure:size>");
278		evalAndCompareTo("selSize()", NATNumber.ONE);
279		evalAndCompareTo("lexSize",   "<java closure:size>");
280		evalAndCompareTo("lexSize()", NATNumber.ONE);
281
282		evalAndCompareTo("selElementCount",   "<native closure:elementCount>");
283		evalAndCompareTo("selElementCount()", NATNumber.ONE);
284		evalAndCompareTo("lexElementCount",   "<native closure:elementCount>");
285		evalAndCompareTo("lexElementCount()", NATNumber.ONE);	
286	}
287	
288	/**
289	 * Tests whether abstraction can be made over the accessor or a slot,
290	 * independent of whether a slot is implemened as a field or as a pair
291	 * of methods.
292	 */
293	public void testMutatorSelection() {
294		evalAndReturn("def v; def pair := object: {" +
295				"def x := 1;" +
296				"def y() {v};" +
297				"def y:=(v2) { v := v2; v } }");
298		
299		// test mutator for field x
300		evalAndCompareTo("def xmutator := pair.&x:=", "<native closure:x:=>");
301		evalAndCompareTo("xmutator(2)", "2");
302		evalAndTestException("xmutator()", XArityMismatch.class);
303		evalAndCompareTo("pair.x", "2");
304		
305		// test mutator for virtual field y
306		evalAndCompareTo("def ymutator := pair.&y:=", "<closure:y:=>");
307		evalAndCompareTo("ymutator(2)", "2");
308		evalAndTestException("ymutator()", XArityMismatch.class);
309		evalAndCompareTo("pair.y", "2");
310	}
311	
312	/**
313	 * Tests how invocation, calling, lookup and select interact with
314	 * fields, methods and fields bound to closures. Primarly, the uniform
315	 * access principle is demonstrated by its ability to abstract over whether
316	 * a field is implemented as a genuine field or as a set of methods.
317	 */
318	public void testUniformAccessPrinciple() {
319		evalAndReturn(
320				"def o := object: {" +
321				"  def c := { 1 };" +
322				"  def x := 2;" +
323				"  def m() { 3 };" +
324				"  def m:=(v) { 4 }" +
325				"}");
326		
327		// dynamic uniform access
328		evalAndCompareTo("o.x", NATNumber.atValue(2));
329		evalAndCompareTo("o.m", atThree_); // for methods: o.m == o.m()
330		evalAndCompareTo("o.x()", NATNumber.atValue(2));
331		evalAndCompareTo("o.m()", atThree_);
332		evalAndCompareTo("o.c", "<closure:lambda>"); // for closures: o.c != o.c()
333		evalAndCompareTo("o.c()", NATNumber.ONE);
334		
335		evalAndCompareTo("o.x := 2", "2"); // assigns the field
336		evalAndCompareTo("o.m := 0", "4"); // invokes the mutator
337		evalAndCompareTo("o.c := { 1 }", "<closure:lambda>"); // assigns the field
338		
339		evalAndCompareTo("o.&x", "<native closure:x>"); // for fields: & returns accessor
340		evalAndCompareTo("o.&m", "<closure:m>"); // for methods: & returns method closure
341		evalAndCompareTo("o.&c", "<native closure:c>"); // for closures: & returns accessor
342		evalAndCompareTo("o.&x:=", "<native closure:x:=>"); // for fields: & returns mutator
343		evalAndCompareTo("o.&m:=", "<closure:m:=>"); // for methods: & returns method closure
344		evalAndCompareTo("o.&c:=", "<native closure:c:=>"); // for closures: & returns mutator
345		
346		// lexical uniform access
347		evalAndCompareTo("withScope: o do: { x }", "2");
348		evalAndCompareTo("withScope: o do: { m }", "3"); // for methods: m == m()
349		evalAndCompareTo("withScope: o do: { x() }", "2");
350		evalAndCompareTo("withScope: o do: { m() }", "3");
351		evalAndCompareTo("withScope: o do: { c }", "<closure:lambda>"); // for closures: c != c()
352		evalAndCompareTo("withScope: o do: { c() }", "1");
353		
354		evalAndCompareTo("withScope: o do: { x := 2 }", "2"); // assigns the field
355		evalAndCompareTo("withScope: o do: { m := 0 }", "4"); // invokes the mutator
356		evalAndCompareTo("withScope: o do: { c := { 1 } }", "<closure:lambda>"); // assigns the field
357		
358		evalAndCompareTo("withScope: o do: { &x }", "<native closure:x>"); // for fields: & returns accessor
359		evalAndCompareTo("withScope: o do: { &m }", "<closure:m>"); // for methods: & returns method closure
360		evalAndCompareTo("withScope: o do: { &c }", "<native closure:c>"); // for closures: & returns accessor
361		evalAndCompareTo("withScope: o do: { &x:= }", "<native closure:x:=>"); // for fields: & returns mutator
362		evalAndCompareTo("withScope: o do: { &m:= }", "<closure:m:=>"); // for methods: & returns method closure
363		evalAndCompareTo("withScope: o do: { &c:= }", "<native closure:c:=>"); // for closures: & returns mutator
364		
365	}
366
367	/**
368	 * Tests whether abstraction can be made over the accessor and mutator
369	 * of a slot at the meta-level, independent of whether a slot is implemened
370	 * as a field or as a pair of methods.
371	 */
372	public void testUniformAccessViaMirrors() {
373		evalAndReturn("def rre := 42; def cplx := object: {" +
374				"def clofield := { 5 };" +
375				"def im := 1;" +
376				"def re() { rre };" +
377				"def re:=(v) { rre := v; rre+2 }" +
378				"}");
379		evalAndReturn("def cplxm := reflect: cplx");
380		
381		// test whether selection on mirrors can abstract over fields or methods
382		// or fields containing closures
383		evalAndCompareTo("cplxm.select(cplx, `im)", "<native closure:im>");
384		evalAndCompareTo("cplxm.select(cplx, `re)", "<closure:re>");
385		evalAndCompareTo("cplxm.select(cplx, `im:=)", "<native closure:im:=>");
386		evalAndCompareTo("cplxm.select(cplx, `re:=)", "<closure:re:=>");
387		evalAndCompareTo("cplxm.select(cplx, `clofield)", "<native closure:clofield>");
388		
389		// test whether explicit invocation on mirrors can abstract over fields
390		// or methods or fields containing closures
391		evalAndCompareTo("cplxm.invoke(cplx, .im())", "1");
392		evalAndCompareTo("cplxm.invoke(cplx, .re())", "42");
393		evalAndCompareTo("cplxm.invoke(cplx, `(.#(`im:=)(4)))", "4"); // cplxm.invoke(cplx, .im:=(4)) but parser does not support this (yet)
394		evalAndCompareTo("cplxm.invoke(cplx, `(.#(`re:=)(3)))", "5");
395		evalAndCompareTo("cplxm.invoke(cplx, .clofield())", "5"); // cplx.clofield() = 5
396		evalAndCompareTo("cplxm.invokeField(cplx, `clofield)", "<closure:lambda>"); // cplx.clofield = <lambda>
397	}
398	
399	/**
400	 * This test is written following a bug report where the following happened:
401	 * <code>
402	 * def clo() { 5 }
403	 * &clo<-apply([])@FutureMessage
404	 * </code>
405	 * 
406	 * The interpreter complained that "apply" cound not be found in "5". Hence,
407	 * it applied the closure 'too early' and sent apply to the return value of the
408	 * closure instead.
409	 * 
410	 * The cause: the future message annotation caused the actual message
411	 * being sent to be a real AmbientTalk object that was coerced into an
412	 * {@link ATAsyncMessage}. However, the
413	 * {@link Reflection#downInvocation(ATObject, java.lang.reflect.Method, ATObject[])}
414	 * method failed to recognize a nullary method invocation as a field access
415	 * and hence treated 'msg.receiver' as 'msg.receiver()'. Since 'receiver'
416	 * was bound to a closure, the closure was automatically applied, rather
417	 * than simply being returned.
418	 */
419	public void testCoercedFieldAccess() throws InterpreterException {
420		// originally, the test was performed on actual asynchronous messages because
421		// they encapsulated their receiver. This is no longer true, hence we
422		// test the same principle (whether downInvocation gets it right) on an ATField instead
423		
424		// construct a coerced asynchronous message
425		// ctx_.base_lexicalScope().meta_defineField(AGSymbol.jAlloc("AsyncMsg"), NativeTypeTags._ASYNCMSG_);
426		// ATAsyncMessage msg = evalAndReturn("object: { def receiver := { 5 } } taggedAs: [AsyncMsg]").asAsyncMessage();
427		// rcv should be { 5 }, not 5
428		// ATObject rcv = msg.base_receiver();
429		
430		// construct a coerced field object
431		ctx_.base_lexicalScope().meta_defineField(AGSymbol.jAlloc("Field"), NativeTypeTags._FIELD_);
432		ATField fld = evalAndReturn("object: { def readField := { 5 } } taggedAs: [Field]").asField();
433		// readField should be { 5 }, not 5
434		ATObject val = fld.base_readField();
435		
436		assertFalse(val.meta_isTaggedAs(NativeTypeTags._NUMBER_).asNativeBoolean().javaValue);
437		assertTrue(val.meta_isTaggedAs(NativeTypeTags._CLOSURE_).asNativeBoolean().javaValue);
438		assertEquals(NATNumber.atValue(5), val.asClosure().base_apply(NATTable.EMPTY));
439	}
440	
441}